From 9e792f90bbe857f69188140bc2e5ed13f8950a5b Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 8 Jan 2018 13:02:32 +0900 Subject: [PATCH 01/47] Added a new module for PyInstaller --- Engine/k2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/k2.py b/Engine/k2.py index c1c9d51..a4fe133 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -11,6 +11,7 @@ import xml.etree.cElementTree as ET import json import email +from backports import lzma try: import yara From 3caa6aaa3fb31f528c8b5d7c0d9aabd0e6ecb223 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 8 Jan 2018 14:19:22 +0900 Subject: [PATCH 02/47] Replaced WindowsError to OSError --- Engine/kavcore/k2engine.py | 12 ++++++------ Engine/kavcore/k2file.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index da7e409..47d27d8 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -103,7 +103,7 @@ def set_plugins(self, plugins_path): module = imp.load_source(name, os.path.splitext(kmd_path)[0] + '.py') try: os.remove(os.path.splitext(kmd_path)[0] + '.pyc') - except WindowsError: + except OSError: pass else: k = k2kmdfile.KMD(kmd_path, pu) # 모든 KMD 파일을 복호화한다. @@ -655,7 +655,7 @@ def __quarantine_file(self, filename): shutil.move(filename, t_quarantine_fname) # 격리소로 이동 is_success = True - except (shutil.Error, WindowsError) as e: + except (shutil.Error, OSError) as e: pass if isinstance(self.quarantine_callback_fn, types.FunctionType): @@ -791,7 +791,7 @@ def __update_arc_file_struct(self, p_file_info): try: os.remove(t_fname) # print '[*] Remove :', t_fname - except WindowsError: + except OSError: pass return ret_file_info @@ -833,7 +833,7 @@ def __disinfect_process(self, ret_value, action_type): os.remove(d_fname) d_ret = True self.result['Deleted_files'] += 1 # 삭제 파일 수 - except (IOError, WindowsError) as e: + except (IOError, OSError) as e: d_ret = False t_file_info.set_modify(d_ret) # 치료(수정/삭제) 여부 표시 @@ -948,7 +948,7 @@ def __feature_file(self, file_struct, fileformat, malware_id): fp.close() return ret - except (IOError, EngineKnownError, WindowsError) as e: + except (IOError, EngineKnownError, OSError) as e: pass return False @@ -1167,7 +1167,7 @@ def format(self, file_struct): ret.update(ff) except AttributeError: pass - except (IOError, EngineKnownError, ValueError, WindowsError) as e: + except (IOError, EngineKnownError, ValueError, OSError) as e: pass if mm: diff --git a/Engine/kavcore/k2file.py b/Engine/kavcore/k2file.py index ccc0920..8e8f673 100644 --- a/Engine/kavcore/k2file.py +++ b/Engine/kavcore/k2file.py @@ -24,7 +24,7 @@ def __init__(self, path=None): try: os.mkdir(self.temp_path) break - except (IOError, WindowsError) as e: + except (IOError, OSError) as e: pass pid += 1 @@ -49,7 +49,7 @@ def removetempdir(self): os.rmdir(self.temp_path) self.temp_path = None return True - except (IOError, WindowsError) as e: # 기타 삭제 오류 처리 + except (IOError, OSError) as e: # 기타 삭제 오류 처리 pass return False From 12304d79838755daf1045941b5e7f5d0bcb1f33e Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 8 Jan 2018 14:39:24 +0900 Subject: [PATCH 03/47] Added a module import error --- Engine/k2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/k2.py b/Engine/k2.py index a4fe133..7566493 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -11,9 +11,9 @@ import xml.etree.cElementTree as ET import json import email -from backports import lzma try: + from backports import lzma import yara except ImportError: pass From b77f5f9c585922704486f832e8d998d51a55d7ab Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 8 Jan 2018 14:46:16 +0900 Subject: [PATCH 04/47] Added an exception of AttributeError --- Engine/kavcore/k2file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/kavcore/k2file.py b/Engine/kavcore/k2file.py index 8e8f673..b8d7da4 100644 --- a/Engine/kavcore/k2file.py +++ b/Engine/kavcore/k2file.py @@ -49,7 +49,7 @@ def removetempdir(self): os.rmdir(self.temp_path) self.temp_path = None return True - except (IOError, OSError) as e: # 기타 삭제 오류 처리 + except (IOError, OSError, AttributeError) as e: # 기타 삭제 오류 처리 pass return False From 3291a3126c5a7fdcc5a106123ad67fe872e3f430 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 8 Jan 2018 17:19:58 +0900 Subject: [PATCH 05/47] Modified DDE patterns --- Engine/plugins/dde.py | 63 ++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/Engine/plugins/dde.py b/Engine/plugins/dde.py index 80009d8..17d8ae1 100644 --- a/Engine/plugins/dde.py +++ b/Engine/plugins/dde.py @@ -57,21 +57,14 @@ class KavMain: # 리턴값 : 0 - 성공, 0 이외의 값 - 실패 # --------------------------------------------------------------------- def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 - # 악성코드 DDE 패턴 (https://blog.nviso.be/2017/10/11/detecting-dde-in-ms-office-documents/) - self.dde_ptns = [] + s = r'dde(auto)?\b[\d\D]+?' + self.p_dde1 = re.compile(s, re.IGNORECASE) - s = r'.+?\b[Dd][Dd][Ee][Aa][Uu][Tt][Oo]\b.+?' - self.dde_ptns.append(re.compile(s)) + s = r'\<[\d\D]+?\>' + self.p_tag = re.compile(s) - s = r'.+?\b[Dd][Dd][Ee]\b.+?' - self.dde_ptns.append(re.compile(s)) - - # 의심 명령어 - s = r'(.+?)' - self.cmd1 = re.compile(s, re.IGNORECASE) - - s = r' Date: Tue, 9 Jan 2018 08:22:24 +0900 Subject: [PATCH 06/47] Modified signatures of RTF Exploit --- Engine/plugins/rtf.py | 55 ++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/Engine/plugins/rtf.py b/Engine/plugins/rtf.py index 34522a6..34b59d5 100644 --- a/Engine/plugins/rtf.py +++ b/Engine/plugins/rtf.py @@ -22,8 +22,14 @@ class KavMain: def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 self.verbose = verbose - cve_2010_3333 = r'\\\bsv\b.*?(\d+);' - self.prog_cve_2010_3333 = re.compile(cve_2010_3333) + cve_2010_3333_magic = r'\bpfragments\b' + self.cve_2010_3333_magic = re.compile(cve_2010_3333_magic, re.IGNORECASE) + + cve_2010_3333_1 = r'pfragments\b[\d\D]*?\\sv\b[\d\D]*?(\d+)|\\sv\b[\d\D]*?(\d+)[\d\D]*?pfragments\b' + self.prog_cve_2010_3333_1 = re.compile(cve_2010_3333_1, re.IGNORECASE) + + cve_2010_3333_2 = r'\\sn[\W]{1,20}?pfragments\b' + self.prog_cve_2010_3333_2 = re.compile(cve_2010_3333_2, re.IGNORECASE) cve_2014_1761 = r'\\listoverridecount(\d+)' self.prog_cve_2014_1761 = re.compile(cve_2014_1761, re.IGNORECASE) @@ -53,29 +59,39 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 mm = filehandle if mm[:4] == '{\\rt': # RTF 파일 - # CVE-2010-3333 - t = self.prog_cve_2010_3333.search(mm) - if t: - val = int(t.groups()[0]) - if val != 2 and val != 4 and val != 8: - if self.verbose: - print '[*] RTF :', val + # 검색 속도를 위해 pfragments가 존재하는지 먼저 확인 + if self.cve_2010_3333_magic.search(mm): + # CVE-2010-3333 (1) + t = self.prog_cve_2010_3333_1.search(mm) + if t: + val = int(max(t.groups())) - return True, 'Exploit.RTF.CVE-2010-3333', 0, kernel.INFECTED + if val != 2 and val != 4 and val != 8: + if self.verbose: + print '[*] RTF :', val + + return True, 'Exploit.RTF.CVE-2010-3333.a', 0, kernel.INFECTED + + # CVE-2010-3333 (2) + t = self.prog_cve_2010_3333_2.search(mm) + if t: + return True, 'Exploit.RTF.CVE-2010-3333.b', 0, kernel.INFECTED # CVE-2014-1761 t = self.prog_cve_2014_1761.search(mm) if t: - val = t.groups()[0] + val = int(t.groups()[0]) + if self.verbose: print '[*] RTF :', val - t1 = re.findall(r'{\\lfolevel}', mm) - if t1: - if self.verbose: - print '[*] N :', len(t1) - if len(t1) > int(val): - return True, 'Exploit.RTF.CVE-2014-1761', 0, kernel.INFECTED + if val >= 25: + t1 = re.findall(r'{\\lfolevel}', mm) + if t1: + if self.verbose: + print '[*] N :', len(t1) + if len(t1) > val: + return True, 'Exploit.RTF.CVE-2014-1761', 0, kernel.INFECTED else: if kavutil.is_textfile(mm[:4096]): t = self.prog_eps_dropper.search(mm) @@ -112,7 +128,8 @@ def listvirus(self): # 진단 가능한 악성코드 리스트 vlist = list() # 리스트형 변수 선언 # 진단/치료하는 악성코드 이름 등록 - vlist.append('Exploit.RTF.CVE-2010-3333') + vlist.append('Exploit.RTF.CVE-2010-3333.a') + vlist.append('Exploit.RTF.CVE-2010-3333.b') vlist.append('Exploit.RTF.CVE-2014-1761') vlist.append('Trojan.PS.Agent') @@ -130,6 +147,6 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info['version'] = '1.1' # 버전 info['title'] = 'RTF Engine' # 엔진 설명 info['kmd_name'] = 'rtf' # 엔진 파일 이름 - info['sig_num'] = 2 # 진단/치료 가능한 악성코드 수 + info['sig_num'] = len(self.listvirus()) # 진단/치료 가능한 악성코드 수 return info From e0f5da1eed758d96fb390ec6ce59f861a8216bd8 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Tue, 9 Jan 2018 08:23:56 +0900 Subject: [PATCH 07/47] Added KeyboardInterrupt handling --- Engine/kavcore/k2engine.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index 47d27d8..19342ac 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -896,8 +896,10 @@ def __scan_file(self, file_struct, fileformat): fp.close() return ret, vname, mid, scan_state, eid - except (EngineKnownError, ValueError, KeyboardInterrupt) as e: + except (EngineKnownError, ValueError) as e: pass + except KeyboardInterrupt: + raise KeyboardInterrupt except: self.result['IO_errors'] += 1 # 파일 I/O 오류 발생 수 From 130a6bdfaf1af7dec81f3fe63307083b5f5861eb Mon Sep 17 00:00:00 2001 From: hanul93 Date: Tue, 9 Jan 2018 12:02:06 +0900 Subject: [PATCH 08/47] Fixed length of update hash --- Engine/k2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/k2.py b/Engine/k2.py index 7566493..6ca499f 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -525,7 +525,7 @@ def get_download_list(url): download_file(url, 'update.cfg') buf = open('update.cfg', 'r').read() - p_lists = re.compile(r'([A-Fa-f0-9]{64}) (.+)') + p_lists = re.compile(r'([A-Fa-f0-9]{40}) (.+)') lines = p_lists.findall(buf) for line in lines: From ad4896e2a11b8dfb97e6b7dd1342b2a623141960 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 10 Jan 2018 11:19:17 +0900 Subject: [PATCH 09/47] Fixed DDE signatures --- Engine/plugins/dde.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/plugins/dde.py b/Engine/plugins/dde.py index 17d8ae1..8edec45 100644 --- a/Engine/plugins/dde.py +++ b/Engine/plugins/dde.py @@ -102,7 +102,7 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 buf = data[off:end] if len(buf): t = self.p_tag.sub('', buf).lower() - if t.find('\\system\\cmd.exe'): + if t.find('\\\\system32\\\\cmd.exe') != -1: return True, 'Exploit.MSWord.DDE.a', 0, kernel.INFECTED elif filename_ex.lower() == 'worddocument': data = filehandle @@ -111,7 +111,7 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 buf = s.group() if len(buf): t = self.p_tag.sub('', buf).lower() - if t.find('\\system\\cmd.exe'): + if t.find('\\\\system32\\\\cmd.exe') != -1: return True, 'Exploit.MSWord.DDE.b', 0, kernel.INFECTED except IOError: From 6e1f357ed074be73f49478cc193aab96962604e5 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 10 Jan 2018 12:55:17 +0900 Subject: [PATCH 10/47] Fixed data error during decompression --- Engine/plugins/nsis.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Engine/plugins/nsis.py b/Engine/plugins/nsis.py index 08fcfac..1ab2f33 100644 --- a/Engine/plugins/nsis.py +++ b/Engine/plugins/nsis.py @@ -92,6 +92,7 @@ class NSIS: TYPE_LZMA = 0 TYPE_BZIP = 1 TYPE_ZLIB = 2 + TYPE_COPY = 3 def __init__(self, filename, verbose): self.verbose = verbose @@ -170,14 +171,18 @@ def read(self, filename): # print comp_type if comp_type == self.TYPE_LZMA: try: # 전체 압축한 경우인지 확인해 본다. - data = pylzma.decompress(fdata) + obj = pylzma.decompressobj(maxlength=12) + data = obj.decompress(fdata) except TypeError: pass elif comp_type == self.TYPE_ZLIB: - try: - data = zlib.decompress(fdata, -15) - except zlib.error: - pass + if kavutil.get_uint32(self.body_data, foff) & 0x80000000 == 0x80000000: + try: + data = zlib.decompress(fdata, -15) + except zlib.error: + pass + else: + data = fdata # TYPE_COPY return data else: return None @@ -229,7 +234,8 @@ def do_decompress(self, comp_type, off, size): comp_success = True if comp_type == self.TYPE_LZMA: try: # 전체 압축한 경우인지 확인해 본다. - uncmp_data = pylzma.decompress(self.mm[off:off+size]) + obj = pylzma.decompressobj(maxlength=12) + uncmp_data = obj.decompress(self.mm[0x1c:]) except TypeError: comp_success = False elif comp_type == self.TYPE_ZLIB: @@ -429,6 +435,7 @@ def namelist_ex(self): fl.sort() return fl + # ------------------------------------------------------------------------- # KavMain 클래스 # ------------------------------------------------------------------------- @@ -466,7 +473,7 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'NSIS Engine' # 엔진 설명 info['kmd_name'] = 'nsis' # 엔진 파일 이름 From a06d10a2aa0ea96f576e68b0acb98bf14c19ae83 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 10 Jan 2018 14:09:01 +0900 Subject: [PATCH 11/47] Modified DDE patterns --- Engine/plugins/dde.py | 56 +++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/Engine/plugins/dde.py b/Engine/plugins/dde.py index 8edec45..0c86a32 100644 --- a/Engine/plugins/dde.py +++ b/Engine/plugins/dde.py @@ -45,6 +45,16 @@ def is_include_exe(s): return False +def InstrSub(obj): + text = obj.groups()[0] + + off = text.find('QUOTE') # QUOTE가 존재하나? + if off != -1: + t = text[off+5:].strip().split(' ') + text = ''.join([chr(int(x)) for x in t]) + + return text + # ------------------------------------------------------------------------- # KavMain 클래스 # ------------------------------------------------------------------------- @@ -57,8 +67,17 @@ class KavMain: # 리턴값 : 0 - 성공, 0 이외의 값 - 실패 # --------------------------------------------------------------------- def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 - s = r'dde(auto)?\b[\d\D]+?' - self.p_dde1 = re.compile(s, re.IGNORECASE) + s = r'"begin"(.+?)"end"' + self.p_dde_text = re.compile(s, re.IGNORECASE) + + s = r'' + self.p_instr = re.compile(s, re.IGNORECASE) + + s = r'\bdde(auto)?\b' + self.p_dde = re.compile(s, re.IGNORECASE) + + s = r'\\system32\b(.+)\.exe' + self.p_cmd = re.compile(s, re.IGNORECASE) s = r'\<[\d\D]+?\>' self.p_tag = re.compile(s) @@ -94,15 +113,28 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 data = get_zip_data(filename, 'word/document.xml') if data: - s = self.p_dde1.search(data) - if s: - off = s.span()[0] - end = s.span()[1] - - buf = data[off:end] + # TEXT 영역을 추출한다. + texts = self.p_dde_text.findall(data) + if len(texts): + buf = '' + for text in texts: + # 앞쪽 begin Tag 제거 + off = text.find('>') + text = text[off+1:] + + # 뒤쪽 end Tag 제거 + off = text.rfind('<') + text = text[:off] + + # instr를 처리한다. + text = self.p_instr.sub(InstrSub, text) + + # 모든 Tag 삭제 + buf += self.p_tag.sub('', text) + '\n' + + # print buf if len(buf): - t = self.p_tag.sub('', buf).lower() - if t.find('\\\\system32\\\\cmd.exe') != -1: + if self.p_dde.search(buf) and self.p_cmd.search(buf): return True, 'Exploit.MSWord.DDE.a', 0, kernel.INFECTED elif filename_ex.lower() == 'worddocument': data = filehandle @@ -110,10 +142,8 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 if s: buf = s.group() if len(buf): - t = self.p_tag.sub('', buf).lower() - if t.find('\\\\system32\\\\cmd.exe') != -1: + if self.p_dde.search(buf) and self.p_cmd.search(buf): return True, 'Exploit.MSWord.DDE.b', 0, kernel.INFECTED - except IOError: pass From 99e127965a629b7a875c6d2a4b7ac1fbe2a04581 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 09:14:19 +0900 Subject: [PATCH 12/47] Modified detection of rtf signature --- Engine/plugins/rtf.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Engine/plugins/rtf.py b/Engine/plugins/rtf.py index 34b59d5..c31fd58 100644 --- a/Engine/plugins/rtf.py +++ b/Engine/plugins/rtf.py @@ -46,6 +46,24 @@ def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 def uninit(self): # 플러그인 엔진 종료 return 0 # 플러그인 엔진 종료 성공 + # --------------------------------------------------------------------- + # format(self, filehandle, filename, filename_ex) + # 파일 포맷을 분석한다. + # 입력값 : filehandle - 파일 핸들 + # filename - 파일 이름 + # filename_ex - 압축 파일 내부 파일 이름 + # 리턴값 : {파일 포맷 분석 정보} or None + # --------------------------------------------------------------------- + def format(self, filehandle, filename, filename_ex): + ret = {} + + mm = filehandle + + if mm[:4] == '{\\rt': # RTF 파일 + ret['ff_rtf'] = 'RTF' + + return ret + # --------------------------------------------------------------------- # scan(self, filehandle, filename, fileformat) # 악성코드를 검사한다. @@ -58,7 +76,8 @@ def uninit(self): # 플러그인 엔진 종료 def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 mm = filehandle - if mm[:4] == '{\\rt': # RTF 파일 + # 미리 분석된 파일 포맷중에 RTF 포맷이 있는가? + if 'ff_rtf' in fileformat: # 검색 속도를 위해 pfragments가 존재하는지 먼저 확인 if self.cve_2010_3333_magic.search(mm): # CVE-2010-3333 (1) From fd0cc855629995df2b7902dcc964501e657b18c8 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 10:08:07 +0900 Subject: [PATCH 13/47] Added extract objdata in rtf file --- Engine/plugins/rtf.py | 241 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/Engine/plugins/rtf.py b/Engine/plugins/rtf.py index c31fd58..26a8ac0 100644 --- a/Engine/plugins/rtf.py +++ b/Engine/plugins/rtf.py @@ -4,10 +4,191 @@ import os import re +import mmap import kernel import kavutil +# ------------------------------------------------------------------------- +# objdata 추출 관련 함수들 +# ------------------------------------------------------------------------- +p_rtf_tags = re.compile(r'\\([^\\{}]*)') +p_rtf_tag = re.compile(r'\\\s*(#|\*|[a-z\x00]*)(\d*)(.*)', re.I) +p_obj_tag = re.compile(r'\\objdata\b', re.I) + + +# {} 개수를 체크해서 최종 닫혀진 괄호까지의 데이터를 추출한다. +def extract_data(buf): + return __sub_extract_data(buf)[0] + + +def join_data(tag, num, data): + s = '' + if tag: + s += tag + if num: + s += num + if data: + s += data + + return s + + +def __keyword_sub(obj): + s = '' + + tag, num, data = obj.groups() + + if tag.strip() in ['#', '*', '']: + s += join_data(None, num, data) + elif tag == 'bin': + n = int(num, 16) + d = data.lstrip() + + s += join_data(None, None, d[:n].encode('hex') + d[n:]) + else: + pass + # print '[*] Key :', tag + # s += join_data(None, None, data) + + return s + + +def __keyword_process(data): + s = '' + + buf_len = len(data) + off = 0 + + while off < buf_len: + c = data[off] + if c == '\\': + p = p_rtf_tags.match(data[off:]) + if p: + t = p.group() + x = p_rtf_tag.sub(__keyword_sub, t) + s += x + off += len(t) + else: + s += c + off += 1 + + return s + + +def __sub_extract_data(data): + ret = '' + + len_buf = len(data) + off = 0 + while off < len_buf: + c = data[off] + off += 1 + + if c == '{': + x, l = __sub_extract_data(data[off:]) + ret += x + off += l + elif c == '}': + return ret, off + elif c == '\\': + p = p_rtf_tags.search(data[off - 1:]) + if p: + x = p.group() + xx = __keyword_process(x) + ret += xx + off += len(x) - 1 + else: + ret += c + + if len(ret): + return ret, off + + +# ------------------------------------------------------------------------- +# RtfFile 클래스 +# ------------------------------------------------------------------------- +class RtfFile: + def __init__(self, filename, verbose=False): + self.verbose = verbose # 디버깅용 + self.filename = filename + self.fp = None + self.mm = None + + self.p = re.compile(r'[A-Fa-f0-9]+') + + self.num_objdata = 0 # RTF에 삽입된 objdata의 수 + self.objdata = {} # objdata + self.parse() + + def parse(self): + try: + self.fp = open(self.filename, 'rb') + self.mm = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ) + + mm = self.mm + + if mm[:4] != '{\\rt': # 헤더 체크 + self.close() + return None + + self.num_objdata = len(p_obj_tag.findall(mm)) + if self.verbose: + print '[*] objdata : %d' % self.num_objdata + + # objdata를 추출한다. + i = 1 + for obj in p_obj_tag.finditer(mm): + end_off = obj.span()[1] + data = extract_data(mm[end_off:]) + + hex_data = ''.join(self.p.findall(data)) + + if hex_data[:16] == '0105000002000000': # Magic + h = hex_data + name_len = int(h[22:24] + h[20:22] + h[18:20] + h[16:18], 16) + name = h[24:24 + (name_len * 2) - 2].decode('hex') + off = 24 + (name_len * 2) + 16 # Unknown 4Byte * 2 + data_len = int(h[off + 6:off + 8] + h[off + 4:off + 6] + h[off + 2:off + 4] + h[off:off + 2], 16) + + if self.verbose: + print name_len + print name + print hex(data_len) + + t = h[24 + (name_len * 2) + 24:24 + (name_len * 2) + 24 + (data_len * 2)] + + if self.verbose: + print hex(len(t)) + + obj_name = 'RTF #%d' % i + self.objdata[obj_name] = t.decode('hex') + i += 1 + except IOError: + pass + + def close(self): + if self.mm: + self.mm.close() + self.mm = None + + if self.fp: + self.fp.close() + self.fp = None + + def namelist(self): + names = [] + + if len(self.objdata): + names = self.objdata.keys() + names.sort() + + return names + + def read(self, fname): + return self.objdata.get(fname, None) + + # ------------------------------------------------------------------------- # KavMain 클래스 # ------------------------------------------------------------------------- @@ -21,6 +202,7 @@ class KavMain: # --------------------------------------------------------------------- def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 self.verbose = verbose + self.handle = {} # 압축 파일 핸들 cve_2010_3333_magic = r'\bpfragments\b' self.cve_2010_3333_magic = re.compile(cve_2010_3333_magic, re.IGNORECASE) @@ -169,3 +351,62 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info['sig_num'] = len(self.listvirus()) # 진단/치료 가능한 악성코드 수 return info + + # --------------------------------------------------------------------- + # __get_handle(self, filename) + # 압축 파일의 핸들을 얻는다. + # 입력값 : filename - 파일 이름 + # 리턴값 : 압축 파일 핸들 + # --------------------------------------------------------------------- + def __get_handle(self, filename): + if filename in self.handle: # 이전에 열린 핸들이 존재하는가? + zfile = self.handle.get(filename, None) + else: + zfile = RtfFile(filename) # rtf 파일 열기 + self.handle[filename] = zfile + + return zfile + + # --------------------------------------------------------------------- + # arclist(self, filename, fileformat) + # 압축 파일 내부의 파일 목록을 얻는다. + # 입력값 : filename - 파일 이름 + # fileformat - 파일 포맷 분석 정보 + # 리턴값 : [[압축 엔진 ID, 압축된 파일 이름]] + # --------------------------------------------------------------------- + def arclist(self, filename, fileformat): + file_scan_list = [] # 검사 대상 정보를 모두 가짐 + + # 미리 분석된 파일 포맷중에 RTF 포맷이 있는가? + if 'ff_rtf' in fileformat: + zfile = self.__get_handle(filename) + + for name in zfile.namelist(): + file_scan_list.append(['arc_rtf', name]) + + return file_scan_list + + # --------------------------------------------------------------------- + # unarc(self, arc_engine_id, arc_name, fname_in_arc) + # 입력값 : arc_engine_id - 압축 엔진 ID + # arc_name - 압축 파일 + # fname_in_arc - 압축 해제할 파일 이름 + # 리턴값 : 압축 해제된 내용 or None + # --------------------------------------------------------------------- + def unarc(self, arc_engine_id, arc_name, fname_in_arc): + if arc_engine_id == 'arc_rtf': + zfile = self.__get_handle(arc_name) + data = zfile.read(fname_in_arc) + return data + + return None + + # --------------------------------------------------------------------- + # arcclose(self) + # 압축 파일 핸들을 닫는다. + # --------------------------------------------------------------------- + def arcclose(self): + for fname in self.handle.keys(): + zfile = self.handle[fname] + zfile.close() + self.handle.pop(fname) From 3efe9bff55bac04fcf8f2275c99d3091339ffc24 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 10:08:52 +0900 Subject: [PATCH 14/47] Modified pps names of ole file --- Engine/plugins/ole.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Engine/plugins/ole.py b/Engine/plugins/ole.py index e14e247..f09dfbf 100644 --- a/Engine/plugins/ole.py +++ b/Engine/plugins/ole.py @@ -353,7 +353,12 @@ def parse(self): t_size = min(kavutil.get_uint16(pps, 0x40), 0x40) if t_size != 0: - p['Name'] = DecodeStreamName(pps[0:t_size-2]).decode('UTF-16LE', 'replace') + # 출력시 이름이 깨질 가능성이 큼 + if ord(pps[0]) & 0xF0 == 0x00 and ord(pps[1]) == 0x00: + name = '_\x00' + pps[2:t_size-2] + else: + name = pps[0:t_size-2] + p['Name'] = DecodeStreamName(name).decode('UTF-16LE', 'replace') else: p['Name'] = '' From 338cc1c529605464ffea920148e0214000ea6bb7 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 11:46:15 +0900 Subject: [PATCH 15/47] Added a CVE-2012-0158 signature --- Engine/plugins/ole.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Engine/plugins/ole.py b/Engine/plugins/ole.py index f09dfbf..e330fb6 100644 --- a/Engine/plugins/ole.py +++ b/Engine/plugins/ole.py @@ -321,6 +321,13 @@ def parse(self): print kavutil.HexDump().Buffer(self.root, 0, 0x80) + # CVE-2012-0158 검사하기 + # Root Entry에 ListView.2의 CLSID가 존재함 + # 참고 : https://securelist.com/the-curious-case-of-a-cve-2012-0158-exploit/37158/ + if self.root[0x50:0x60] == '\x4B\xF0\xD1\xBD\x8B\x85\xD1\x11\xB1\x6A\x00\xC0\xF0\x28\x36\x28': + self.exploit.append('Exploit.OLE.CVE-2012-0158') + return False + # sbd 읽기 sbd_startblock = kavutil.get_uint32(self.mm, 0x3c) num_of_sbd_blocks = kavutil.get_uint32(self.mm, 0x40) From 8e6ba388c9d2f300f53b7dd98b6c009733f6c618 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 13:59:40 +0900 Subject: [PATCH 16/47] Added CVE-2012-0158 signatures --- Engine/plugins/ole.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/Engine/plugins/ole.py b/Engine/plugins/ole.py index e330fb6..ce60097 100644 --- a/Engine/plugins/ole.py +++ b/Engine/plugins/ole.py @@ -321,13 +321,6 @@ def parse(self): print kavutil.HexDump().Buffer(self.root, 0, 0x80) - # CVE-2012-0158 검사하기 - # Root Entry에 ListView.2의 CLSID가 존재함 - # 참고 : https://securelist.com/the-curious-case-of-a-cve-2012-0158-exploit/37158/ - if self.root[0x50:0x60] == '\x4B\xF0\xD1\xBD\x8B\x85\xD1\x11\xB1\x6A\x00\xC0\xF0\x28\x36\x28': - self.exploit.append('Exploit.OLE.CVE-2012-0158') - return False - # sbd 읽기 sbd_startblock = kavutil.get_uint32(self.mm, 0x3c) num_of_sbd_blocks = kavutil.get_uint32(self.mm, 0x40) @@ -377,6 +370,20 @@ def parse(self): p['Size'] = kavutil.get_uint32(pps, 0x78) p['Valid'] = False + # CVE-2012-0158 검사하기 + # pps에 ListView.2의 CLSID가 존재함 + # 참고 : https://securelist.com/the-curious-case-of-a-cve-2012-0158-exploit/37158/ + # 참고 : https://www.symantec.com/security_response/attacksignatures/detail.jsp?asid=25657 + cve_clsids = ['\x4B\xF0\xD1\xBD\x8B\x85\xD1\x11\xB1\x6A\x00\xC0\xF0\x28\x36\x28', + '\xE0\xF5\x6B\x99\x44\x80\x50\x46\xAD\xEB\x0B\x01\x39\x14\xE9\x9C', + '\xE6\x3F\x83\x66\x83\x85\xD1\x11\xB1\x6A\x00\xC0\xF0\x28\x36\x28', + '\x5F\xDC\x81\x91\x7D\xE0\x8A\x41\xAC\xA6\x8E\xEA\x1E\xCB\x8E\x9E', + '\xB6\x90\x41\xC7\x89\x85\xD1\x11\xB1\x6A\x00\xC0\xF0\x28\x36\x28' + ] + if pps[0x50:0x60] in cve_clsids: + self.exploit.append('Exploit.OLE.CVE-2012-0158') + return False + self.pps.append(p) # PPS Tree 검증 @@ -1495,13 +1502,30 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'OLE Library' # 엔진 설명 info['kmd_name'] = 'ole' # 엔진 파일 이름 info['make_arc_type'] = kernel.MASTER_PACK # 악성코드 치료 후 재압축 유무 + info['sig_num'] = len(self.listvirus()) # 진단/치료 가능한 악성코드 수 return info + # --------------------------------------------------------------------- + # listvirus(self) + # 진단/치료 가능한 악성코드의 리스트를 알려준다. + # 리턴값 : 악성코드 리스트 + # --------------------------------------------------------------------- + def listvirus(self): # 진단 가능한 악성코드 리스트 + vlist = list() # 리스트형 변수 선언 + + vlist.append('Exploit.OLE.CVE-2012-0158') # 진단/치료하는 악성코드 이름 등록 + vlist.append('Exploit.OLE.CVE-2003-0820') + vlist.append('Exploit.OLE.CVE-2003-0347') + + vlist.sort() + + return vlist + # --------------------------------------------------------------------- # format(self, filehandle, filename, filename_ex) # 파일 포맷을 분석한다. From 11b8c939cc4dee304fe331c05149bc913de48531 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 12 Jan 2018 16:08:52 +0900 Subject: [PATCH 17/47] Fixed infinite loop problem due to pps link error --- Engine/plugins/ole.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Engine/plugins/ole.py b/Engine/plugins/ole.py index ce60097..87a9089 100644 --- a/Engine/plugins/ole.py +++ b/Engine/plugins/ole.py @@ -442,6 +442,7 @@ def parse(self): # PPS Tree의 유효성을 체크한다. (내장) # --------------------------------------------------------------------- def __valid_pps_tree(self): + scaned_pps_node = [0] # 이미 분석한 노드의 경우 더이상 분석하지 않기 위해 처리 f = [] if len(self.pps) == 0: # 분석된 PPS가 없으면 종료 @@ -449,6 +450,7 @@ def __valid_pps_tree(self): if self.pps[0]['Dir'] != 0xffffffff and self.pps[0]['Type'] == 5: f.append(self.pps[0]['Dir']) + scaned_pps_node.append(self.pps[0]['Dir']) self.pps[0]['Valid'] = True if len(f) == 0: # 정상적인 PPS가 없음 @@ -471,13 +473,25 @@ def __valid_pps_tree(self): self.pps[x]['Valid'] = True if self.pps[x]['Prev'] != 0xffffffff: - f.append(self.pps[x]['Prev']) + if self.pps[x]['Prev'] in scaned_pps_node: + self.pps[x]['Prev'] = 0xffffffff + else: + f.append(self.pps[x]['Prev']) + scaned_pps_node.append(self.pps[x]['Prev']) if self.pps[x]['Next'] != 0xffffffff: - f.append(self.pps[x]['Next']) + if self.pps[x]['Next'] in scaned_pps_node: + self.pps[x]['Next'] = 0xffffffff + else: + f.append(self.pps[x]['Next']) + scaned_pps_node.append(self.pps[x]['Next']) if self.pps[x]['Dir'] != 0xffffffff: - f.append(self.pps[x]['Dir']) + if self.pps[x]['Dir'] in scaned_pps_node: + self.pps[x]['Dir'] = 0xffffffff + else: + f.append(self.pps[x]['Dir']) + scaned_pps_node.append(self.pps[x]['Dir']) return True From a6c46f9468aacbc730958b81237698e81763ae79 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 15 Jan 2018 09:04:54 +0900 Subject: [PATCH 18/47] Fixed infinite loop problem due to checkpe error (upx) --- Engine/plugins/upx.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Engine/plugins/upx.py b/Engine/plugins/upx.py index e473216..9ed1bd6 100644 --- a/Engine/plugins/upx.py +++ b/Engine/plugins/upx.py @@ -197,10 +197,8 @@ def RebuildPE(src, ssize, dst, dsize, ep, upx0, upx1, magic, dend): if not pehdr and dend > (0xF8 + 0x28): pehdr = dend - 0xF8 - 0x28 - if int32(pehdr) < 0: - raise SystemError - while True: + while int32(pehdr) > 0: sections, valign, sectcnt = checkpe(dst, dsize, pehdr) if sections: break @@ -271,7 +269,7 @@ def RebuildPE(src, ssize, dst, dsize, ep, upx0, upx1, magic, dend): for ch in newbuf: upx_d += ch except: - return None + return '' return upx_d @@ -610,15 +608,18 @@ def unarc(self, arc_engine_id, arc_name, fname_in_arc): except OverflowError: raise ValueError - if unpack_data == '': # 압축 해제 실패 - raise ValueError - if self.verbose: kavutil.vprint('Decompress') kavutil.vprint(None, 'Compressed Size', '%d' % len(data)) - kavutil.vprint(None, 'Decompress Size', '%d' % len(unpack_data)) + if unpack_data == '': # 압축 해제 실패 + kavutil.vprint(None, 'Decompress Size', 'Error') + else: + kavutil.vprint(None, 'Decompress Size', '%d' % len(unpack_data)) print + if unpack_data == '': # 압축 해제 실패 + raise ValueError + data = unpack_data except IOError: pass From a9231876a812e3ab49feb1ce41767b4d0afede7a Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 15 Jan 2018 09:51:35 +0900 Subject: [PATCH 19/47] Added exception handling for invalid resources in pe file --- Engine/plugins/pe.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Engine/plugins/pe.py b/Engine/plugins/pe.py index 790f85a..8cbd261 100644 --- a/Engine/plugins/pe.py +++ b/Engine/plugins/pe.py @@ -140,6 +140,7 @@ def enum(*sequential, **named): class PE: def __init__(self, mm, verbose, filename): self.filename = filename + self.filesize = os.path.getsize(filename) self.verbose = verbose self.mm = mm self.sections = [] # 모든 섹션 정보 담을 리스트 @@ -263,6 +264,9 @@ def parse(self): try: rsrc_off, _ = self.rva_to_off(rsrc_rva) # 리소스 위치 변환 + if rsrc_off > self.filesize: + raise ValueError + if len(mm[rsrc_off:rsrc_off + rsrc_size]) != rsrc_size: # 충분한 리소스가 존재하지 않음 raise ValueError @@ -286,6 +290,9 @@ def parse(self): # Name ID name_id_off = (name_id_off & 0x7FFFFFFF) + rsrc_off + if name_id_off > self.filesize: + raise ValueError + num_name_id_name = kavutil.get_uint16(mm, name_id_off + 0xC) num_name_id_id = kavutil.get_uint16(mm, name_id_off + 0xE) @@ -296,6 +303,9 @@ def parse(self): # 리소스 영역의 최종 이름 생성 if name_id_id & 0x80000000 == 0x80000000: string_off = (name_id_id & 0x7FFFFFFF) + rsrc_off + if string_off > self.filesize: + raise ValueError + len_name = kavutil.get_uint16(mm, string_off) rsrc_name_id_name = mm[string_off + 2:string_off + 2 + (len_name * 2):2] string_name = rsrc_type_name + '/' + rsrc_name_id_name @@ -304,6 +314,9 @@ def parse(self): # Language language_off = (language_off & 0x7FFFFFFF) + rsrc_off + if language_off > self.filesize: + raise ValueError + num_language_name = kavutil.get_uint16(mm, language_off + 0xC) num_language_id = kavutil.get_uint16(mm, language_off + 0xE) @@ -315,7 +328,12 @@ def parse(self): data_rva = kavutil.get_uint32(mm, data_entry_off) data_off, _ = self.rva_to_off(data_rva) + if data_off > self.filesize: + continue + data_size = kavutil.get_uint32(mm, data_entry_off + 4) + if data_size > self.filesize: + continue if data_size > 8192: # 최소 8K 이상인 리소스만 데이터로 추출 if 'Resource_UserData' in pe_format: From c55c1b8e29959bf02a5384182fcbbb2f0d9b6811 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 15 Jan 2018 12:40:22 +0900 Subject: [PATCH 20/47] Fixed processing of ASN.1 data length --- Engine/plugins/adware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Engine/plugins/adware.py b/Engine/plugins/adware.py index 2313b8b..d0e8578 100644 --- a/Engine/plugins/adware.py +++ b/Engine/plugins/adware.py @@ -73,10 +73,9 @@ def get_asn1_len(self, data): if val & 0x80 == 0: return val, 2 else: - data_type = {1: 'B', 2: 'H', 4: 'L'} data_len = val & 0x7f - val = struct.unpack('>' + data_type[data_len], data[2:2+data_len])[0] + val = int(data[2:2 + data_len].encode('hex'), 16) return val, 2+data_len # ASN1의 데이터를 얻는다. From 5ed5d867d0022d8f5057ee87ed27beca7e16ea42 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 15 Jan 2018 12:41:05 +0900 Subject: [PATCH 21/47] Fixed number of signatures --- Engine/plugins/kavutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/plugins/kavutil.py b/Engine/plugins/kavutil.py index 8605e82..979b6a5 100644 --- a/Engine/plugins/kavutil.py +++ b/Engine/plugins/kavutil.py @@ -228,7 +228,7 @@ def get_sig_num(self, sig_key): if buf[0:4] == 'KAVS': sig_num += get_uint32(buf, 4) except IOError: - return None + continue return sig_num From 8b68584aff05fa19dd8ba86c63c7a58991e4871d Mon Sep 17 00:00:00 2001 From: hanul93 Date: Tue, 16 Jan 2018 08:00:35 +0900 Subject: [PATCH 22/47] Added crc32 hash function --- Engine/plugins/cryptolib.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Engine/plugins/cryptolib.py b/Engine/plugins/cryptolib.py index 7cbf819..66c4122 100644 --- a/Engine/plugins/cryptolib.py +++ b/Engine/plugins/cryptolib.py @@ -3,6 +3,7 @@ import hashlib +import zlib from ctypes import c_ushort @@ -16,6 +17,16 @@ def md5(data): return hashlib.md5(data).hexdigest() +# ------------------------------------------------------------------------- +# crc32(data) +# 주어진 데이터에 대해 CRC32 해시를 구한다. +# 입력값 : data - 데이터 +# 리턴값 : CRC32 해시 문자열 +# ------------------------------------------------------------------------- +def crc32(data): + return '%08x' % (zlib.crc32(data) & 0xffffffff) + + # ------------------------------------------------------------------------- # sha256(data) # 주어진 데이터에 대해 SHA256 해시를 구한다. From 5bdcf8e0b9ed39988e779c4aad5d5839da2b2cb9 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Thu, 18 Jan 2018 10:41:00 +0900 Subject: [PATCH 23/47] Added virus scan engine --- Engine/plugins/kavutil.py | 221 ++++++++++++++++++++++++++++++- Engine/plugins/kicom.lst | 1 + Engine/plugins/ve.py | 268 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 Engine/plugins/ve.py diff --git a/Engine/plugins/kavutil.py b/Engine/plugins/kavutil.py index 979b6a5..5556647 100644 --- a/Engine/plugins/kavutil.py +++ b/Engine/plugins/kavutil.py @@ -31,6 +31,7 @@ def vprint(header, section=None, msg=None): # 악성코드 패턴 인스턴스 # ------------------------------------------------------------------------- handle_pattern_md5 = None # 악성코드 패턴 핸들 (MD5 해시) +handle_pattern_vdb = None # 악성코드 패턴 핸들 (VDB) # ------------------------------------------------------------------------- # 정규표현식 컴파일 @@ -250,6 +251,217 @@ def get_sig_vlist(self, sig_key): return sig_vname + +# ------------------------------------------------------------------------- +# PatternVDB +# ------------------------------------------------------------------------- +class PatternVDB: + # --------------------------------------------------------------------- + # __init__(self, plugins_path) + # 악성코드 패턴을 초기화한다. + # 인력값 : plugins_path - 악성코드 패턴의 위치 + # --------------------------------------------------------------------- + def __init__(self, plugins_path): + self.sig_sizes = {} + self.sig_p1s = {} + self.sig_p2s = {} + self.sig_names = {} + self.sig_times = {} # 메모리 관리를 위해 시간 정보를 가짐 + self.plugins = plugins_path + + fl = glob.glob(os.path.join(plugins_path, 've.s??')) + fl.sort() + for name in fl: + obj = p_md5_pattern_ext.search(name) + if obj: + idx = obj.groups()[0] # ex:01 + sig_key = os.path.split(name)[1].lower().split('.')[0] # ex:script + sp = self.__load_sig(name) + if sp is None: + continue + + if len(sp): # 로딩된 패턴이 1개 이상이면... + if not (sig_key in self.sig_sizes): + self.sig_sizes[sig_key] = {} + + for psize in sp.keys(): + if psize in self.sig_sizes[sig_key]: + self.sig_sizes[sig_key][psize][idx].append(psize) + else: + self.sig_sizes[sig_key][psize] = {idx: sp[psize]} + + # --------------------------------------------------------------------- + # match_size(self, sig_key, sig_size) + # 지정한 악성코드 패턴을 해당 크기가 존재하는지 확인한다. + # 인력값 : sig_key - 지정한 악성코드 패턴 + # : sig_size - 크기 + # 리턴값 : 악성코드 패턴 내부에 해당 크기가 존재하는지 여부 (True or False) + # --------------------------------------------------------------------- + def match_size(self, sig_key, sig_size): + sig_key = sig_key.lower() # 대문자로 입력될 가능성 때문에 모두 소문자로 변환 + + if sig_key in self.sig_sizes: # sig_key가 로딩되어 있나? + if sig_size in self.sig_sizes[sig_key].keys(): + return self.sig_sizes[sig_key][sig_size] + + return None + + # --------------------------------------------------------------------- + # get_cs1(self, ve_id, idx) + # 1차 패턴을 읽는다. + # 입력값 : ve_id - ve 패턴의 파일 + # : idx - 내부 인덱스 + # 리턴값 : 1차 패턴 + # --------------------------------------------------------------------- + def get_cs1(self, ve_id, idx): + sig_key = 've' + + if self.__load_sig_ex(self.sig_p1s, 'i', sig_key, ve_id): + return self.sig_p1s[sig_key][ve_id][idx] + + return None + + # --------------------------------------------------------------------- + # get_cs2(self, ve_id, idx) + # 2차 패턴을 읽는다. + # 입력값 : ve_id - ve 패턴의 파일 + # : idx - 내부 인덱스 + # 리턴값 : 2차 패턴 + # --------------------------------------------------------------------- + def get_cs2(self, ve_id, idx): + sig_key = 've' + + if self.__load_sig_ex(self.sig_p2s, 'c', sig_key, ve_id): + return self.sig_p2s[sig_key][ve_id][idx] + + return None + + # --------------------------------------------------------------------- + # get_vname(self, ve_id, vname_id) + # 악설코드 이름을 리턴한다. + # 입력값 : ve_id - ve 패턴의 파일 + # : vname_id - 내부 인덱스 + # 리턴값 : 1차 패턴 + # --------------------------------------------------------------------- + def get_vname(self, ve_id, vname_id): + sig_key = 've' + + if self.__load_sig_ex(self.sig_names, 'n', sig_key, ve_id): + return self.sig_names[sig_key][ve_id][vname_id] + + return None + + # --------------------------------------------------------------------- + # __load_sig(self, fname) + # 악성코드 패턴을 로딩한다 + # 인력값 : fname - 악성코드 패틴 파일 이름 + # 리턴값 : 악성코드 패턴 자료 구조 + # --------------------------------------------------------------------- + def __load_sig(self, fname): + try: + data = open(fname, 'rb').read() + if data[0:4] == 'KAVS': + sp = marshal.loads(zlib.decompress(data[12:])) + return sp + except IOError: + return None + + # --------------------------------------------------------------------- + # __load_sig_ex(self, sig_dict, sig_prefix, sig_key, idx) + # 악성코드 패턴을 로딩한다. + # 단, 어떤 자료구조에 로딩되는지의 여부도 결정할 수 있다. + # 인력값 : sig_dict - 악성코드 패틴이 로딩될 자료 구조 + # sig_prefix - 악성코드 패턴 이름 중 확장자 prefix + # sig_key - 악성코드 패턴 이름 중 파일 이름 + # idx - 악성코드 패턴 이름 중 확장자 번호 + # 리턴값 : 악성코드 패턴 로딩 성공 여부 + # --------------------------------------------------------------------- + def __load_sig_ex(self, sig_dict, sig_prefix, sig_key, idx): # (self.sig_names, 'n', 'script', '01') + if not (sig_key in sig_dict) or not (idx in sig_dict[sig_key]): + # 패턴 로딩 + try: + name_fname = os.path.join(self.plugins, '%s.%s%s' % (sig_key, sig_prefix, idx)) + sp = self.__load_sig(name_fname) + if sp is None: + return False + except IOError: + return False + + sig_dict[sig_key] = {idx: sp} + + # 현재 시간을 sig_time에 기록한다. + if not (sig_key in self.sig_times): + self.sig_times[sig_key] = {} + + if not (sig_prefix in self.sig_times[sig_key]): + self.sig_times[sig_key][sig_prefix] = {} + + self.sig_times[sig_key][sig_prefix][idx] = time.time() + + return True + + # --------------------------------------------------------------------- + # __save_mem(self) + # 오랫동안 사용하지 않은 악성코드 패턴을 메모리에서 제거한다. + # --------------------------------------------------------------------- + def __save_mem(self): + # 정리해야 할 패턴이 있을까? (3분 이상 사용되지 않은 패턴) + n = time.time() + for sig_key in self.sig_times.keys(): + for sig_prefix in self.sig_times[sig_key].keys(): + for idx in self.sig_times[sig_key][sig_prefix].keys(): + # print '[-]', n - self.sig_times[sig_key][sig_prefix][idx] + if n - self.sig_times[sig_key][sig_prefix][idx] > 4: # (3 * 60) : + # print '[*] Delete sig : %s.%s%s' % (sig_key, sig_prefix, idx) + if sig_prefix == 'i': # 1차 패턴 + self.sig_p1s[sig_key].pop(idx) + elif sig_prefix == 'c': # 2차 패턴 + self.sig_p2s[sig_key].pop(idx) + elif sig_prefix == 'n': # 악성코드 이름 패턴 + self.sig_names[sig_key].pop(idx) + + self.sig_times[sig_key][sig_prefix].pop(idx) # 시간 + + # --------------------------------------------------------------------- + # get_sig_num(self, sig_key) + # 주어진 sig_key에 해당하는 악성코드 패턴의 누적된 수를 알려준다. + # 입력값 : sig_key - 악성코드 패턴 이름 (ex:script) + # 리턴값 : 악성코드 패턴 수 + # --------------------------------------------------------------------- + def get_sig_num(self, sig_key): + sig_num = 0 + + fl = glob.glob(os.path.join(self.plugins, '%s.c??' % sig_key)) + + for fname in fl: + try: + buf = open(fname, 'rb').read(12) + if buf[0:4] == 'KAVS': + sig_num += get_uint32(buf, 4) + except IOError: + continue + + return sig_num + + # --------------------------------------------------------------------- + # get_sig_vlist(self, sig_key) + # 주어진 sig_key에 해당하는 악성코드 패턴의 악성코드 이름를 알려준다. + # 입력값 : sig_key - 악성코드 패턴 이름 (ex:script) + # 리턴값 : 악성코드 이름 + # --------------------------------------------------------------------- + def get_sig_vlist(self, sig_key): + sig_vname = [] + fl = glob.glob(os.path.join(self.plugins, '%s.n??' % sig_key)) + + for fname in fl: + try: + sig_vname += self.__load_sig(fname) + except IOError: + return None + + return sig_vname + + # ------------------------------------------------------------------------- # AhoCorasick 클래스 # 원본 : https://gist.github.com/atdt/875e0dba6a15e3fa6018 @@ -491,12 +703,12 @@ def get_uint64(buf, off): def normal_vname(vname, platform=None): # vname = vname.replace('', 'not-a-virus:') vname = vname.replace('', '') - + if platform: vname = vname.replace('

', platform) return vname - + # ---------------------------------------------------------------------------- # Feature를 위한 로직 @@ -583,7 +795,10 @@ class KavMain: def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 # 악성코드 패턴 초기화 global handle_pattern_md5 + global handle_pattern_vdb + handle_pattern_md5 = PatternMD5(plugins_path) + handle_pattern_vdb = PatternVDB(plugins_path) return 0 # 플러그인 엔진 초기화 성공 @@ -604,7 +819,7 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'KicomAV Utility Library' # 엔진 설명 info['kmd_name'] = 'kavutil' # 엔진 파일 이름 diff --git a/Engine/plugins/kicom.lst b/Engine/plugins/kicom.lst index 85e646e..fee353f 100644 --- a/Engine/plugins/kicom.lst +++ b/Engine/plugins/kicom.lst @@ -5,6 +5,7 @@ ole.kmd dummy.kmd eicar.kmd emalware.kmd +ve.kmd adware.kmd macro.kmd dde.kmd diff --git a/Engine/plugins/ve.py b/Engine/plugins/ve.py new file mode 100644 index 0000000..bb6383d --- /dev/null +++ b/Engine/plugins/ve.py @@ -0,0 +1,268 @@ +# -*- coding:utf-8 -*- +# Author: Kei Choi(hanul93@gmail.com) +# virus 유형의 악성코드 검사 엔진 + + +import os +import types +import kernel +import kavutil +import cryptolib + + +# ------------------------------------------------------------------------- +# Signature 파일의 구조 +# ------------------------------------------------------------------------- +# Flag + Word(해당 Flag에 Offset은 항상 0 위치의 2Byte) +# Flag +# 0 : 파일의 처음 +# 1 : 실행 위치 (DOS-EP) +# 2 : 실행 위치 (PE-EP) +# 3 : 각 섹션의 처음 (PE, ELF 등) +# Checksum1 : Flag, Offset, Length, CRC32 +# Checksum2 : Flag, Offset, Length, CRC32 +# MalwareName +# ------------------------------------------------------------------------- +# Example: 0000 F8A8:02, 0000, 0000, XXXXXXXX:02, 0000, 0000, XXXXXXXX:MalwareName +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# 주어진 버퍼에서 특정 크기별로 미리 패턴을 만들어 둔다. +# ------------------------------------------------------------------------- +def gen_checksums(buf): + patterns = [] + + # 처음 10개는 앞쪽 6, 7, 8, 9 ... 0xF + for i in range(6, 0x10): + patterns.append(int(gen_checksum(buf, 0, i), 16)) + + # 나머지 15개는 0x10, 0x18, 0x20 ... 0x80 + for i in range(0x10, 0x88, 8): + patterns.append(int(gen_checksum(buf, 0, i), 16)) + + return patterns + + +def gen_checksum(buf, off, size): + return cryptolib.crc32(buf[off:off+size]) + + +# ------------------------------------------------------------------------- +# KavMain 클래스 +# ------------------------------------------------------------------------- +class KavMain: + # --------------------------------------------------------------------- + # init(self, plugins_path) + # 플러그인 엔진을 초기화 한다. + # 인력값 : plugins_path - 플러그인 엔진의 위치 + # verbose - 디버그 모드 (True or False) + # 리턴값 : 0 - 성공, 0 이외의 값 - 실패 + # --------------------------------------------------------------------- + def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 + self.verbose = verbose + + self.flags_off = {} # 각 flag의 위치 정보를 담는다. + return 0 # 플러그인 엔진 초기화 성공 + + # --------------------------------------------------------------------- + # uninit(self) + # 플러그인 엔진을 종료한다. + # 리턴값 : 0 - 성공, 0 이외의 값 - 실패 + # --------------------------------------------------------------------- + def uninit(self): # 플러그인 엔진 종료 + return 0 # 플러그인 엔진 종료 성공 + + # --------------------------------------------------------------------- + # getinfo(self) + # 플러그인 엔진의 주요 정보를 알려준다. (제작자, 버전, ...) + # 리턴값 : 플러그인 엔진 정보 + # --------------------------------------------------------------------- + def getinfo(self): # 플러그인 엔진의 주요 정보 + info = dict() # 사전형 변수 선언 + + info['author'] = 'Kei Choi' # 제작자 + info['version'] = '1.0' # 버전 + info['title'] = 'Virus Engine' # 엔진 설명 + info['kmd_name'] = 've' # 엔진 파일 이름 + info['sig_num'] = kavutil.handle_pattern_vdb.get_sig_num('ve') # 진단/치료 가능한 악성코드 수 + + return info + + # --------------------------------------------------------------------- + # listvirus(self) + # 진단/치료 가능한 악성코드의 리스트를 알려준다. + # 리턴값 : 악성코드 리스트 + # --------------------------------------------------------------------- + def listvirus(self): # 진단 가능한 악성코드 리스트 + vlist = kavutil.handle_pattern_vdb.get_sig_vlist('ve') + vlist.sort() + + vlists = [] + for vname in vlist: + vlists.append(kavutil.normal_vname(vname)) + + return vlists + + # --------------------------------------------------------------------- + # scan(self, filehandle, filename, fileformat) + # 악성코드를 검사한다. + # 입력값 : filehandle - 파일 핸들 + # filename - 파일 이름 + # fileformat - 파일 포맷 + # filename_ex - 파일 이름 (압축 내부 파일 이름) + # 리턴값 : (악성코드 발견 여부, 악성코드 이름, 악성코드 ID) 등등 + # --------------------------------------------------------------------- + def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 + try: + self.flags_off = {} + flags = [] + mm = filehandle + + # Flag별 Signature를 만든다. + # Flag - 0 : 파일의 처음 + flags.append([int('0000' + mm[0:2].encode('hex'), 16), gen_checksums(mm[0:0x80])]) + self.flags_off[0] = [0] + + # Flag - 1 : DOS EP + # TODO + + # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? + if 'ff_pe' in fileformat: + # Flag - 2 : PE EP + ff = fileformat['ff_pe'] + ep_off = ff['pe']['EntryPointRaw'] + flags.append([int('0002' + mm[ep_off:ep_off+2].encode('hex'), 16), + gen_checksums(mm[ep_off:ep_off+0x80])]) + self.flags_off[2] = [ep_off] + + # Flag - 3 : 각 섹션의 헤더 + flag3_off = [] + for idx, section in enumerate(ff['pe']['Sections']): + fsize = section['SizeRawData'] + foff = section['PointerRawData'] + flags.append([int('0003' + mm[foff:foff + 2].encode('hex'), 16), + gen_checksums(mm[foff:foff+0x80])]) + flag3_off.append(foff) + self.flags_off[3] = flag3_off + + cs_size = [6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x18, + 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, + 0x68, 0x70, 0x78, 0x80] + + if self.verbose: + print '-' * 79 + kavutil.vprint('Engine') + kavutil.vprint(None, 'Engine', 've.kmd') + kavutil.vprint(None, 'File name', os.path.split(filename)[-1]) + kavutil.vprint(None, 'MD5', cryptolib.md5(mm[:])) + + print + kavutil.vprint('VE') + vdb_name = os.path.split(filename)[-1] + '.vdb' + kavutil.vprint(None, 'VDB File name', vdb_name) + + fp = open(vdb_name, 'w') + + for flag in flags: + # kavutil.vprint(None, 'Flag', '%08X' % flag[0]) + msg = 'Flag : %08x\n' % flag[0] + fp.write(msg) + + for i, cs in enumerate(flag[1]): + # kavutil.vprint(None, 'CS = %02X' % cs_pos[i], cs) + msg = 'CS = %02x : %s\n' % (cs_size[i], cs) + fp.write(msg) + fp.write('\n') + + fp.close() + + for flag in flags: + p1 = kavutil.handle_pattern_vdb.match_size('ve', flag[0]) # 일치하는 Flag가 있나? + # print '%08x :' % flag[0], p1 + # print flag[0] >> 16 + + if p1: + for ve_id in p1.keys(): + for idx in p1[ve_id]: + cs1 = kavutil.handle_pattern_vdb.get_cs1(ve_id, idx) + + cs1_flag = cs1[0] + cs1_off = cs1[1] + cs1_size = cs1[2] + cs1_crc = cs1[3] + + if flag[0] >> 16 == cs1_flag and cs1_off == 0 and cs1_size in cs_size: + i = cs_size.index(cs1_size) + # print '=', hex(flag[1][i]) + if cs1_crc == flag[1][i]: # 1차 패턴이 같은가? + vname = self.__scan_cs2(mm, ve_id, idx) + if vname: + return True, vname, 0, kernel.INFECTED + else: + buf = self.__get_data_crc32(mm, cs1_flag, cs1_off, cs1_size) + if cs1_crc == int(gen_checksum(mm, cs1_off, cs1_size), 16): + vname = self.__scan_cs2(mm, ve_id, idx) + if vname: + return True, vname, 0, kernel.INFECTED + except IOError: + pass + + kavutil.handle_pattern_vdb.__save_mem() # 메모리 용량을 낮추기 위해 사용 + + # 악성코드를 발견하지 못했음을 리턴한다. + return False, '', -1, kernel.NOT_FOUND + + # --------------------------------------------------------------------- + # disinfect(self, filename, malware_id) + # 악성코드를 치료한다. + # 입력값 : filename - 파일 이름 + # : malware_id - 치료할 악성코드 ID + # 리턴값 : 악성코드 치료 여부 + # --------------------------------------------------------------------- + def disinfect(self, filename, malware_id): # 악성코드 치료 + return False # 치료 실패 리턴 + + # --------------------------------------------------------------------- + # __get_data_crc32(self, buf, flag, off, size) + # 특정 위치의 crc32를 얻는다. + # 입력값 : buf - 버퍼 + # : flag - 읽을 위치 정보 (Base 위치) + # : off - 상대 거리 + # : size - 계산할 버퍼 크기 + # 리턴값 : 계산된 crc32 + # --------------------------------------------------------------------- + def __get_data_crc32(self, buf, flag, off, size): + crc32s = [] + for base_offs in self.flags_off[flag]: + if isinstance(base_offs, types.ListType): + for base_off in base_offs: + crc32s.append(int(gen_checksum(buf, base_off+off, size), 16)) + else: + crc32s.append(int(gen_checksum(buf, base_offs + off, size), 16)) + + return crc32s + + # --------------------------------------------------------------------- + # __scan_cs2(self, mm, ve_id, idx) + # 2차 패턴을 검사한다. + # 입력값 : mm - 버퍼 + # : ve_id - ve 패턴의 파일 + # : idx - 내부 인덱스 + # 리턴값 : 발견된 악성코드 이름 + # --------------------------------------------------------------------- + def __scan_cs2(self, mm, ve_id, idx): + cs2 = kavutil.handle_pattern_vdb.get_cs2(ve_id, idx) + cs2_flag = cs2[0] + cs2_off = cs2[1] + cs2_size = cs2[2] + cs2_crc = cs2[3] + vname_id = cs2[4] + + crc32s = self.__get_data_crc32(mm, cs2_flag, cs2_off, cs2_size) + if cs2_crc in crc32s: # 패턴 일치 + vname = kavutil.handle_pattern_vdb.get_vname(ve_id, vname_id) + if vname: + return kavutil.normal_vname(vname) + + return None From 3a8d112e376980bf54d643140cb719cccc7abdc7 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Thu, 18 Jan 2018 10:45:37 +0900 Subject: [PATCH 24/47] Added sigtool for ve engine --- Tools/sigtool_vdb.py | 213 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 Tools/sigtool_vdb.py diff --git a/Tools/sigtool_vdb.py b/Tools/sigtool_vdb.py new file mode 100644 index 0000000..1cef8c8 --- /dev/null +++ b/Tools/sigtool_vdb.py @@ -0,0 +1,213 @@ +# -*- coding:utf-8 -*- +# Author: Kei Choi(hanul93@gmail.com) + + +import re +import sys +import os +import struct +import marshal +import zlib + +s = os.path.dirname( + os.path.dirname( + os.path.abspath(__file__) + ) +) + os.sep + 'Engine' + os.sep + 'kavcore' + +sys.path.append(s) + +import k2timelib + + +# ------------------------------------------------------------------------- +# 한 파일에 생성할 악성코드 패턴 최대 개수 +# ------------------------------------------------------------------------- +MAX_COUNT = 100000 + +# ------------------------------------------------------------------------- +# virus.db 파일에서 사용할 주석문 정규표현식 +# ------------------------------------------------------------------------- +re_comment = r'#.*' + +# ------------------------------------------------------------------------- +# 자료 구조 +# ------------------------------------------------------------------------- +size_sig = {} # 크기와 ID 저장 +p1_sig = [] # CS1 +p2_sig = [] # CS2 +name_sig = [] # 악성코드 이름 + + +def printProgress(_off, _all): + if _off != 0: + percent = (_off * 100.) / _all + + s_num = int(percent / 5) + space_num = 20 - s_num + + sys.stdout.write('[*] Download : [') + sys.stdout.write('#' * s_num) + sys.stdout.write(' ' * space_num) + sys.stdout.write('] ') + sys.stdout.write('%3d%% (%d/%d)\r' % (int(percent), _off, _all)) + + +# ------------------------------------------------------------------------- +# 텍스트 라인을 분석해서 악성코드 패턴을 위한 자료구조를 만든다. +# ------------------------------------------------------------------------- +def add_signature(line): + t = line.split(':') + + size = int(t[0], 16) # flag를 기존 mdb의 크기처럼 처리 단, 16진수로 처리됨 + + cs1 = t[1].split(',') # CS1 + cs1_flag = int(cs1[0].strip(), 16) + cs1_off = int(cs1[1].strip(), 16) + cs1_size = int(cs1[2].strip(), 16) + cs1_crc32 = int(cs1[3].strip(), 16) + + cs2 = t[2].split(',') # CS2 + cs2_flag = int(cs2[0].strip(), 16) + cs2_off = int(cs2[1].strip(), 16) + cs2_size = int(cs2[2].strip(), 16) + cs2_crc32 = int(cs2[3].strip(), 16) + + name = t[3] + + # 크기 추가 + sig_id = size_sig.get(size, []) + sig_id.append(len(p1_sig)) + size_sig[size] = sig_id + + p1_sig.append([cs1_flag, cs1_off, cs1_size, cs1_crc32]) + + if name in name_sig: # 이미 등록된 이름이면 id만 획득 + name_id = name_sig.index(name) + else: + name_id = len(name_sig) + name_sig.append(name) + + p2_sig.append([cs2_flag, cs2_off, cs2_size, cs2_crc32, name_id]) + + +# ------------------------------------------------------------------------- +# 자료구조에 담긴 정보를 악성코드 패턴 파일로 저장한다. +# ------------------------------------------------------------------------- +def save_signature(fname, _id): + # 현재 날짜와 시간을 구한다. + ret_date = k2timelib.get_now_date() + ret_time = k2timelib.get_now_time() + + # 날짜와 시간 값을 2Byte로 변경한다. + val_date = struct.pack('= MAX_COUNT: + print '[*] %s : %d' % (fname, _id) + save_sig_file(fname, _id) + idx = 0 + _id += 1 + + fp.close() + + save_sig_file(fname, _id) + + +# ------------------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------------------- +if __name__ == '__main__': + if len(sys.argv) < 2: + print 'Usage : sigtool_vdb.py [sig text] [id]' + exit(0) + + if len(sys.argv) == 2: + sin_fname = sys.argv[1] + _id = 1 + elif len(sys.argv) == 3: + sin_fname = sys.argv[1] + _id = int(sys.argv[2]) + + make_signature(sin_fname, _id) From f8f8fee830946878de04cf9b8005fe044b86c5f4 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 19 Jan 2018 07:48:12 +0900 Subject: [PATCH 25/47] Added scan for Exploit.JS.Agent.gen --- Engine/plugins/hwp.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Engine/plugins/hwp.py b/Engine/plugins/hwp.py index 5af4d04..9d19573 100644 --- a/Engine/plugins/hwp.py +++ b/Engine/plugins/hwp.py @@ -75,6 +75,9 @@ class KavMain: def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 self.handle = {} self.hwp_ole = re.compile('bindata/bin\d+\.ole$', re.IGNORECASE) + + s = r'n\x00e\x00w\x00(\x20\x00)+A\x00c\x00t\x00i\x00v\x00e\x00X\x00O\x00b\x00j\x00e\x00c\x00t\x00' + self.hwp_js = re.compile(s, re.IGNORECASE) return 0 # 플러그인 엔진 초기화 성공 # --------------------------------------------------------------------- @@ -94,11 +97,11 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'HWP Engine' # 엔진 설명 info['kmd_name'] = 'hwp' # 엔진 파일 이름 info['make_arc_type'] = kernel.MASTER_DELETE # 악성코드 치료는 삭제로... - info['sig_num'] = 1 # 진단/치료 가능한 악성코드 수 + info['sig_num'] = len(self.listvirus()) # 진단/치료 가능한 악성코드 수 return info @@ -111,6 +114,9 @@ def listvirus(self): # 진단 가능한 악성코드 리스트 vlist = list() # 리스트형 변수 선언 vlist.append('Exploit.HWP.Generic') # 진단/치료하는 악성코드 이름 등록 + vlist.append('Exploit.JS.Agent.gen') # 진단/치료하는 악성코드 이름 등록 + + vlist.sort() return vlist @@ -135,6 +141,9 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 ret, tagid = scan_hwp_recoard(mm, len(mm)) if ret is False: # 레코드 추적 실패 return True, 'Exploit.HWP.Generic.%02X' % tagid, 0, kernel.INFECTED + elif filename_ex.lower().find('scripts/defaultjscript') >= 0: + if self.hwp_js.search(mm): + return True, 'Exploit.JS.Agent.gen', 0, kernel.INFECTED # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND From 678439f37f840f74ee8e496088708f8c0259436d Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 19 Jan 2018 12:11:57 +0900 Subject: [PATCH 26/47] Modified temp path --- Engine/kavcore/k2engine.py | 11 ++++--- Engine/kavcore/k2file.py | 60 ++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index 19342ac..f033373 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -64,6 +64,11 @@ def __del__(self): # 키콤백신이 만든 임시 파일 모두 제거 self.temp_path.removetempdir() + try: # 해당 pid 폴더도 삭제한다. + shutil.rmtree(self.temp_path.temp_path) + except OSError: + pass + # --------------------------------------------------------------------- # set_plugins(self, plugins_path) # 주어진 경로에서 플러그인 엔진을 로딩 준비한다. @@ -146,12 +151,10 @@ def set_plugins(self, plugins_path): # --------------------------------------------------------------------- # set_temppath(self, temp_path) # 주어진 임시 폴더를 설정한다. - # 인자값 : temp_path - 임시 폴더 클래스 - # 리턴값 : 성공 여부 # --------------------------------------------------------------------- - def set_temppath(self, temp_path): + def set_temppath(self): # 임시 폴더를 지정한다. - self.temp_path = k2file.K2Tempfile(temp_path) + self.temp_path = k2file.K2Tempfile() # --------------------------------------------------------------------- # create_instance(self) diff --git a/Engine/kavcore/k2file.py b/Engine/kavcore/k2file.py index b8d7da4..19ecec4 100644 --- a/Engine/kavcore/k2file.py +++ b/Engine/kavcore/k2file.py @@ -3,33 +3,27 @@ import os +import re import glob +import shutil import tempfile +import psutil # --------------------------------------------------------------------- # K2Tempfile 클래스 # --------------------------------------------------------------------- class K2Tempfile: - def __init__(self, path=None): - if not path: # 임시 폴더 설정이 없으면 운영체제의 임시 폴더로 설정 - self.temp_path = tempfile.gettempdir() - else: - i = 0 - pid = os.getpid() - - while i < 5: - self.temp_path = os.path.join(path, 'tmp%05x' % pid) - if not os.path.exists(self.temp_path): - try: - os.mkdir(self.temp_path) - break - except (IOError, OSError) as e: - pass + def __init__(self): + self.re_pid = re.compile(r'ktmp([0-9a-f]{5})$', re.IGNORECASE) + + pid = os.getpid() + self.temp_path = os.path.join(tempfile.gettempdir(), 'ktmp%05x' % pid) - pid += 1 - i += 1 # 5번만 폴더 만들기 시도 - else: + if not os.path.exists(self.temp_path): + try: + os.mkdir(self.temp_path) + except (IOError, OSError) as e: self.temp_path = tempfile.gettempdir() def gettempdir(self): @@ -39,20 +33,22 @@ def mktemp(self): return tempfile.mktemp(prefix='ktmp', dir=self.temp_path) def removetempdir(self): - tpath = os.path.join(self.temp_path, 'ktmp*') - fl = glob.glob(tpath) - try: - for name in fl: - if os.path.isfile(name): - os.remove(name) - else: - os.rmdir(self.temp_path) - self.temp_path = None - return True - except (IOError, OSError, AttributeError) as e: # 기타 삭제 오류 처리 - pass - - return False + fl = glob.glob(os.path.join(tempfile.gettempdir(), 'ktmp*')) + if len(fl): + for tname in fl: + if os.path.isdir(tname): + tpath = self.re_pid.search(tname) + if tpath: # 정상적으로 임시 폴더가 생겼음 + if psutil.pid_exists(int(tpath.groups()[0], 16)) is False: + try: + shutil.rmtree(tname) + except OSError: + pass + elif os.path.isfile(tname): + try: + os.remove(tname) + except OSError: + pass # ------------------------------------------------------------------------- From 121e90f7012efd268da7d5ab0dd6ce17af0a111b Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 19 Jan 2018 17:45:55 +0900 Subject: [PATCH 27/47] Modified temp path --- Engine/k2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index 6ca499f..4dfb57a 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -48,7 +48,7 @@ # 주요 상수 # ------------------------------------------------------------------------- KAV_VERSION = '0.29' -KAV_BUILDDATE = 'Jan 08 2018' +KAV_BUILDDATE = 'Jan 19 2018' KAV_LASTYEAR = KAV_BUILDDATE[len(KAV_BUILDDATE)-4:] g_options = None # 옵션 @@ -1026,7 +1026,7 @@ def main(): return 0 # 임시 폴더 설정 - k2.set_temppath(k2_pwd) + k2.set_temppath() kav = k2.create_instance() # 백신 엔진 인스턴스 생성 if not kav: From 45693d116805248d44489d0ba33f48b190b1d978 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 19 Jan 2018 18:43:36 +0900 Subject: [PATCH 28/47] Fixed errors when updating from another folder --- Engine/k2.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index 4dfb57a..de90a08 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -410,14 +410,14 @@ def print_options(): # update_kicomav() # 키콤백신 최신 버전을 업데이트 한다 # ------------------------------------------------------------------------- -def update_kicomav(): +def update_kicomav(path): print try: url = 'https://raw.githubusercontent.com/hanul93/kicomav-db/master/update_v3/' # 서버 주소를 나중에 바꿔야 한다. # 업데이트해야 할 파일 목록을 구한다. - down_list = get_download_list(url) + down_list = get_download_list(url, path) is_k2_exe_update = 'k2.exe' in down_list while len(down_list) != 0: @@ -425,10 +425,10 @@ def update_kicomav(): # 파일 한개씩 업데이트 한다. if filename != 'k2.exe': - download_file(url, filename, gz=True, fnhook=hook) + download_file(url, filename, path, gz=True, fnhook=hook) if is_k2_exe_update: - k2temp_path = download_file_k2(url, 'k2.exe', gz=True, fnhook=hook) + k2temp_path = download_file_k2(url, 'k2.exe', path, gz=True, fnhook=hook) # 업데이트 완료 메시지 출력 cprint('\n[', FOREGROUND_GREY) @@ -436,7 +436,7 @@ def update_kicomav(): cprint(']\n', FOREGROUND_GREY) # 업데이트 설정 파일 삭제 - os.remove('update.cfg') + os.remove(os.path.join(path, 'update.cfg')) # k2.exe의 경우 최종 업데이트 프로그램 실행 if is_k2_exe_update: @@ -453,7 +453,7 @@ def hook(blocknumber, blocksize, totalsize): # 한개의 파일을 다운로드 한다. -def download_file(url, filename, gz=False, fnhook=None): +def download_file(url, filename, path, gz=False, fnhook=None): rurl = url # 업데이트 설정 파일에 있는 목록을 URL 주소로 변환한다 @@ -462,7 +462,8 @@ def download_file(url, filename, gz=False, fnhook=None): rurl += '.gz' # 저장해야 할 파일의 전체 경로를 구한다 - pwd = os.path.join(os.path.abspath(''), filename) + pwd = os.path.join(path, filename) + if gz: pwd += '.gz' @@ -474,7 +475,7 @@ def download_file(url, filename, gz=False, fnhook=None): if gz: data = gzip.open(pwd, 'rb').read() - fname = os.path.join(os.path.abspath(''), filename) + fname = os.path.join(path, filename) open(fname, 'wb').write(data) os.remove(pwd) # gz 파일은 삭제한다. @@ -483,7 +484,7 @@ def download_file(url, filename, gz=False, fnhook=None): # k2.exe를 다운로드 한다. -def download_file_k2(url, filename, gz=False, fnhook=None): +def download_file_k2(url, filename, path, gz=False, fnhook=None): rurl = url # 업데이트 설정 파일에 있는 목록을 URL 주소로 변환한다 @@ -492,7 +493,7 @@ def download_file_k2(url, filename, gz=False, fnhook=None): rurl += '.gz' # 저장해야 할 파일의 전체 경로를 구한다 - pwd = os.path.join(os.path.abspath(''), filename) + pwd = os.path.join(path, filename) if gz: pwd += '.gz' @@ -515,16 +516,16 @@ def download_file_k2(url, filename, gz=False, fnhook=None): # 업데이트 해야 할 파일의 목록을 구한다 -def get_download_list(url): +def get_download_list(url, path): down_list = [] - pwd = os.path.abspath('') + pwd = path try: # 업데이트 설정 파일을 다운로드 한다 - download_file(url, 'update.cfg') + download_file(url, 'update.cfg', pwd) - buf = open('update.cfg', 'r').read() + buf = open(os.path.join(pwd, 'update.cfg'), 'r').read() p_lists = re.compile(r'([A-Fa-f0-9]{40}) (.+)') lines = p_lists.findall(buf) @@ -973,11 +974,14 @@ def main(): print 'Error: %s' % args # 에러 메시지가 담겨 있음 return 0 + # 프로그램이 실행중인 폴더 + k2_pwd = os.path.abspath(os.path.split(sys.argv[0])[0]) + # Help 옵션을 사용한 경우 또는 인자 값이 없는 경우 if options.opt_help or not args: # 인자 값이 없는 업데이트 상황? if options.opt_update: - update_kicomav() + update_kicomav(k2_pwd) return 0 if not options.opt_vlist: # 악성코드 리스트 출력이면 인자 값이 없어도 Help 안보여줌 @@ -1015,9 +1019,6 @@ def main(): # 백신 엔진 구동 k2 = kavcore.k2engine.Engine() # 엔진 클래스 - # 프로그램이 실행중인 폴더 - k2_pwd = os.path.abspath(os.path.split(sys.argv[0])[0]) - # 플러그인 엔진 설정 plugins_path = os.path.join(k2_pwd, 'plugins') if not k2.set_plugins(plugins_path): From 5afe3acbbc598c83fa7d2b9251493a2855fdb85f Mon Sep 17 00:00:00 2001 From: hanul93 Date: Tue, 23 Jan 2018 15:33:27 +0900 Subject: [PATCH 29/47] Fixed errors when updating from another folder --- Engine/k2.py | 4 ++-- Engine/kavcore/k2engine.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index de90a08..2fd8e50 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -48,7 +48,7 @@ # 주요 상수 # ------------------------------------------------------------------------- KAV_VERSION = '0.29' -KAV_BUILDDATE = 'Jan 19 2018' +KAV_BUILDDATE = 'Jan 23 2018' KAV_LASTYEAR = KAV_BUILDDATE[len(KAV_BUILDDATE)-4:] g_options = None # 옵션 @@ -440,7 +440,7 @@ def update_kicomav(path): # k2.exe의 경우 최종 업데이트 프로그램 실행 if is_k2_exe_update: - os.spawnv(os.P_NOWAIT, k2temp_path, (k2temp_path, 'k2', os.path.abspath(''))) + os.spawnv(os.P_NOWAIT, k2temp_path, (k2temp_path, 'k2', path)) except KeyboardInterrupt: cprint('\n[', FOREGROUND_GREY) cprint('Update Stop', FOREGROUND_GREY | FOREGROUND_INTENSITY) diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index f033373..2ddb339 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -149,7 +149,7 @@ def set_plugins(self, plugins_path): return True # --------------------------------------------------------------------- - # set_temppath(self, temp_path) + # set_temppath(self) # 주어진 임시 폴더를 설정한다. # --------------------------------------------------------------------- def set_temppath(self): From bf873e35ade1bc93ab731b8b2ead4705b6c2d3a8 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Thu, 25 Jan 2018 11:55:07 +0900 Subject: [PATCH 30/47] Added a new ZipFile for only KicomAV --- Engine/plugins/zip.py | 299 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 279 insertions(+), 20 deletions(-) diff --git a/Engine/plugins/zip.py b/Engine/plugins/zip.py index 3dfcd7e..e2b4110 100644 --- a/Engine/plugins/zip.py +++ b/Engine/plugins/zip.py @@ -2,9 +2,246 @@ # Author: Kei Choi(hanul93@gmail.com) +import struct +import zlib +import os + import zipfile import kernel + +# 참소 : https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html + +# --------------------------------------------------------------------- +# 엔진 오류 메시지를 정의 +# --------------------------------------------------------------------- +class BadZipTagError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class NeedPasswordError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +# --------------------------------------------------------------------- +# New ZipFile을 위한 구조체 정의 +# --------------------------------------------------------------------- +struct_zipfilerecord = '<5H3L2H' # PK\x03\x04 +size_struct_zipfilerecord = struct.calcsize(struct_zipfilerecord) + +struct_zipdirentry = '<6H3L5H2L' # PK\x01\x02 +size_struct_zipdirentry = struct.calcsize(struct_zipdirentry) + +struct_zipdigitalsig = ' 0: + fname = self.fp.read(fname_len) + fr.append(fname) + else: + fr.append('') + + if extra_len > 0: + fr.append(self.fp.read(extra_len)) + else: + fr.append('') + + if comp_len > 0: + fr.append(self.fp.read(comp_len)) + else: + fr.append('') + + return fr + + def __read_zipdirentry(self): # PK\x01\x02 + data = self.fp.read(size_struct_zipdirentry) + de = struct.unpack(struct_zipdirentry, data) + fname_len = de[9] + extra_len = de[10] + comment_len = de[11] + + de = list(de) + if fname_len > 0: + fname = self.fp.read(fname_len) + de.append(fname) + else: + de.append('') + + if extra_len > 0: + de.append(self.fp.read(extra_len)) + else: + de.append('') + + if comment_len > 0: + de.append(self.fp.read(comment_len)) + else: + de.append('') + + return de + + def __read_zipdigitalsig(self): # PK\x05\x05 + data = self.fp.read(size_struct_zipdigitalsig) + ds = struct.unpack(struct_zipdigitalsig, data) + data_len = ds[0] + + ds = list(ds) + if data_len > 0: + ds.append(self.fp.read(data_len)) + else: + ds.append('') + + return ds + + def __read_zipdatadescr(self): # PK\x07\x08 + data = self.fp.read(size_struct_zipdatadescr) + dd = struct.unpack(struct_zipdatadescr, data) + + return list(dd) + + def __read_zipendlocator(self): # PK\x05\x06 + data = self.fp.read(size_struct_zipendlocator) + el = struct.unpack(struct_zipendlocator, data) + + return list(el) + +''' +if __name__ == '__main__': + z = NZipFile('zip_attatch_data.bin') + z.parse() + for name in z.namelist(): + print name + + print z.read('docProps/app.xml') + + z.close() +''' + # ------------------------------------------------------------------------- # KavMain 클래스 # ------------------------------------------------------------------------- @@ -37,7 +274,7 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'Zip Archive Engine' # 엔진 설명 info['kmd_name'] = 'zip' # 엔진 파일 이름 info['engine_type'] = kernel.ARCHIVE_ENGINE # 엔진 타입 @@ -77,26 +314,35 @@ def format(self, filehandle, filename, filename_ex): zfile = zipfile.ZipFile(filename) # zip 파일 열기 names = zfile.namelist() zfile.close() + + # 파일 포맷은 ZIP이지만 특수 포맷인지를 한번 더 체크한다. + if names is not None: + for name in names: + n = name.lower() + if n == 'classes.dex': + ret['ff_apk'] = 'apk' + elif n == 'xl/workbook.xml': + ret['ff_ooxml'] = 'xlsx' + elif n == 'word/document.xml': + ret['ff_ooxml'] = 'docx' + elif n == 'ppt/presentation.xml': + ret['ff_ooxml'] = 'pptx' + + if len(ret) == 0: + ret['ff_zip'] = 'zip' except zipfile.BadZipfile: - names = None - - # 파일 포맷은 ZIP이지만 특수 포맷인지를 한번 더 체크한다. - if names is not None: - for name in names: - n = name.lower() - if n == 'classes.dex': - ret['ff_apk'] = 'apk' - elif n == 'xl/workbook.xml': - ret['ff_ooxml'] = 'xlsx' - elif n == 'word/document.xml': - ret['ff_ooxml'] = 'docx' - elif n == 'ppt/presentation.xml': - ret['ff_ooxml'] = 'pptx' - - if len(ret) == 0: - ret['ff_zip'] = 'zip' - - return ret + zfile = NZipFile(filename) + if zfile.parse(): + zsize = zfile.get_zipsize() + fsize = os.path.getsize(filename) + zfile.close() + + if zsize < fsize and zsize != 0: + # 파이썬 ZipFile로 해제되지 않는 파일 처리 + # zip 파일 뒤에 attach 된 데이터가 있으면 오류 발생 + ret['ff_attach_zip'] = (zsize, fsize - zsize) + + return ret return None @@ -117,6 +363,10 @@ def arclist(self, filename, fileformat): for name in zfile.namelist(): file_scan_list.append(['arc_zip', name]) # zfile.close() + elif 'ff_attach_zip' in fileformat: + off, zsize = fileformat['ff_attach_zip'] + file_scan_list.append(['arc_attach_zip:0:%d' % off, '#1']) + file_scan_list.append(['arc_attach_zip:%d:%d' % (off, zsize), '#2']) return file_scan_list @@ -135,6 +385,15 @@ def unarc(self, arc_engine_id, arc_name, fname_in_arc): return data except zipfile.BadZipfile: pass + elif arc_engine_id.find('arc_attach_zip') != -1: + t = arc_engine_id.split(':') + off = int(t[1]) + size = int(t[2]) + + with open(arc_name, 'rb') as fp: + fp.seek(off) + data = fp.read(size) + return data return None From f87b58bfe6b8caee5b876cd9a3ab2a520c156368 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Thu, 25 Jan 2018 14:50:32 +0900 Subject: [PATCH 31/47] Modified setting temp directory --- Engine/k2.py | 5 +---- Engine/kavcore/k2engine.py | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index 2fd8e50..0d51838 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -48,7 +48,7 @@ # 주요 상수 # ------------------------------------------------------------------------- KAV_VERSION = '0.29' -KAV_BUILDDATE = 'Jan 23 2018' +KAV_BUILDDATE = 'Jan 25 2018' KAV_LASTYEAR = KAV_BUILDDATE[len(KAV_BUILDDATE)-4:] g_options = None # 옵션 @@ -1026,9 +1026,6 @@ def main(): print_error('KICOM Anti-Virus Engine set_plugins') return 0 - # 임시 폴더 설정 - k2.set_temppath() - kav = k2.create_instance() # 백신 엔진 인스턴스 생성 if not kav: print diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index 2ddb339..efbfb01 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -56,6 +56,8 @@ def __init__(self, verbose=False): # 키콤백신이 만든 임시 파일 모두 제거 (운영체제의 임시 폴더를 초기화) k2file.K2Tempfile().removetempdir() + self.__set_temppath() # 임시 폴더 초기화 + # --------------------------------------------------------------------- # __del__(self) # 클래스를 종료 한다. @@ -149,10 +151,10 @@ def set_plugins(self, plugins_path): return True # --------------------------------------------------------------------- - # set_temppath(self) + # __set_temppath(self) # 주어진 임시 폴더를 설정한다. # --------------------------------------------------------------------- - def set_temppath(self): + def __set_temppath(self): # 임시 폴더를 지정한다. self.temp_path = k2file.K2Tempfile() From 16a4395a0f829d67fb2833f99e2b16b1d30c829a Mon Sep 17 00:00:00 2001 From: hanul93 Date: Thu, 25 Jan 2018 15:11:16 +0900 Subject: [PATCH 32/47] Modified checking crc32 --- Engine/plugins/ve.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Engine/plugins/ve.py b/Engine/plugins/ve.py index bb6383d..6ca19b3 100644 --- a/Engine/plugins/ve.py +++ b/Engine/plugins/ve.py @@ -171,7 +171,7 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 for i, cs in enumerate(flag[1]): # kavutil.vprint(None, 'CS = %02X' % cs_pos[i], cs) - msg = 'CS = %02x : %s\n' % (cs_size[i], cs) + msg = 'CS = %02x : %08x\n' % (cs_size[i], int(cs)) fp.write(msg) fp.write('\n') @@ -234,12 +234,8 @@ def disinfect(self, filename, malware_id): # 악성코드 치료 # --------------------------------------------------------------------- def __get_data_crc32(self, buf, flag, off, size): crc32s = [] - for base_offs in self.flags_off[flag]: - if isinstance(base_offs, types.ListType): - for base_off in base_offs: - crc32s.append(int(gen_checksum(buf, base_off+off, size), 16)) - else: - crc32s.append(int(gen_checksum(buf, base_offs + off, size), 16)) + for base_off in self.flags_off[flag]: + crc32s.append(int(gen_checksum(buf, base_off + off, size), 16)) return crc32s From 5db5342ddb091c0317d7055e29a000dd5e225cab Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 26 Jan 2018 10:31:48 +0900 Subject: [PATCH 33/47] Added BadZipTagError exception --- Engine/plugins/zip.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Engine/plugins/zip.py b/Engine/plugins/zip.py index e2b4110..7d8e97e 100644 --- a/Engine/plugins/zip.py +++ b/Engine/plugins/zip.py @@ -332,15 +332,18 @@ def format(self, filehandle, filename, filename_ex): ret['ff_zip'] = 'zip' except zipfile.BadZipfile: zfile = NZipFile(filename) - if zfile.parse(): - zsize = zfile.get_zipsize() - fsize = os.path.getsize(filename) - zfile.close() - - if zsize < fsize and zsize != 0: - # 파이썬 ZipFile로 해제되지 않는 파일 처리 - # zip 파일 뒤에 attach 된 데이터가 있으면 오류 발생 - ret['ff_attach_zip'] = (zsize, fsize - zsize) + try: + if zfile.parse(): + zsize = zfile.get_zipsize() + fsize = os.path.getsize(filename) + zfile.close() + + if zsize < fsize and zsize != 0: + # 파이썬 ZipFile로 해제되지 않는 파일 처리 + # zip 파일 뒤에 attach 된 데이터가 있으면 오류 발생 + ret['ff_attach_zip'] = (zsize, fsize - zsize) + except BadZipTagError: + pass return ret From f10a4110d5edea1e8c63272b237edb6b6e0d6dc7 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Fri, 2 Feb 2018 17:33:36 +0900 Subject: [PATCH 34/47] Fixed a scan error when there was no file name --- Engine/kavcore/k2engine.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index efbfb01..ddd6f68 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -874,7 +874,10 @@ def __scan_file(self, file_struct, fileformat): filename = file_struct.get_filename() # 검사 대상 파일 이름 추출 filename_ex = file_struct.get_additional_filename() # 압축 내부 파일명 - # 파일 크기가 0이면 악성코드 검사를 할 필요가 없다. + # 파일이 아니거나 크기가 0이면 악성코드 검사를 할 필요가 없다. + if os.path.isfile(filename) is False: + raise EngineKnownError('File is not found!') + if os.path.getsize(filename) == 0: raise EngineKnownError('File Size is Zero!') From af8afd0d4d102a3ff1628e9df007746583c80021 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Feb 2018 15:50:04 +0900 Subject: [PATCH 35/47] Add PDB based malware scan function --- Engine/plugins/emalware.py | 12 ++++++++++-- Engine/plugins/pe.py | 28 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Engine/plugins/emalware.py b/Engine/plugins/emalware.py index 4e8d11d..8d30e65 100644 --- a/Engine/plugins/emalware.py +++ b/Engine/plugins/emalware.py @@ -58,10 +58,10 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.0' # 버전 + info['version'] = '1.1' # 버전 info['title'] = 'eMalware Engine' # 엔진 설명 info['kmd_name'] = 'emalware' # 엔진 파일 이름 - info['sig_num'] = kavutil.handle_pattern_md5.get_sig_num('emalware') + 1 # 진단/치료 가능한 악성코드 수 + info['sig_num'] = kavutil.handle_pattern_md5.get_sig_num('emalware') + 2 # 진단/치료 가능한 악성코드 수 return info @@ -80,6 +80,8 @@ def listvirus(self): # 진단 가능한 악성코드 리스트 for vname in vlist: vlists.append(kavutil.normal_vname(vname)) + vlists.append(kavutil.normal_vname('AdWare.Win32.Sokuxuan.gen')) + return vlists # --------------------------------------------------------------------- @@ -135,6 +137,12 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 vname = kavutil.normal_vname(vname) return True, vname, (0x80000000 + idx), kernel.INFECTED + # case 3. pdb를 이용해서 악성코드 검사 + if 'PDB_Name' in ff['pe']: + if ff['pe']['PDB_Name'].find(':\\pz_git\\bin\\') != -1: + vname = kavutil.normal_vname('AdWare.Win32.Sokuxuan.gen') + return True, vname, 0, kernel.INFECTED + # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] diff --git a/Engine/plugins/pe.py b/Engine/plugins/pe.py index 8cbd261..6e13293 100644 --- a/Engine/plugins/pe.py +++ b/Engine/plugins/pe.py @@ -419,6 +419,28 @@ def parse(self): pe_format['CERTIFICATE_Offset'] = cert_off pe_format['CERTIFICATE_Size'] = cert_size + # Debug 정보 분석 + try: + debug_rva = self.data_directories[image_directory_entry.DEBUG].VirtualAddress # RVA + debug_size = self.data_directories[image_directory_entry.DEBUG].Size # 크기 + if debug_size < 0x1C: + raise ValueError + except (IndexError, ValueError) as e: + debug_rva = 0 + debug_size = 0 + + if debug_rva: # Debug 정보 존재 + t = self.rva_to_off(debug_rva)[0] + debug_off = kavutil.get_uint32(mm, t + 0x18) + debug_size = kavutil.get_uint32(mm, t + 0x10) + + debug_data = mm[debug_off:debug_off + debug_size] + + if debug_data[:4] == 'RSDS': + pe_format['PDB_Name'] = debug_data[0x18:] + else: + pe_format['PDB_Name'] = 'Not support Type : %s' % debug_data[:4] + if self.verbose: print '-' * 79 kavutil.vprint('Engine') @@ -461,6 +483,10 @@ def parse(self): print kavutil.HexDump().Buffer(mm[:], pe_format['EntryPointRaw'], 0x80) print + if 'PDB_Name' in pe_format: + kavutil.vprint('PDB Information') + kavutil.vprint(None, 'Name', '%s' % repr(pe_format['PDB_Name'])) + print except ValueError: return None @@ -516,7 +542,7 @@ def getinfo(self): # 플러그인 엔진의 주요 정보 info = dict() # 사전형 변수 선언 info['author'] = 'Kei Choi' # 제작자 - info['version'] = '1.1' # 버전 + info['version'] = '1.2' # 버전 info['title'] = 'PE Engine' # 엔진 설명 info['kmd_name'] = 'pe' # 엔진 파일 이름 From 86c0d1c7fef076a0349ef73cd6ab5822802da837 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 5 Mar 2018 08:30:55 +0900 Subject: [PATCH 36/47] Fixed problem parsing non-digital signature area --- Engine/plugins/adware.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Engine/plugins/adware.py b/Engine/plugins/adware.py index d0e8578..5beaa4b 100644 --- a/Engine/plugins/adware.py +++ b/Engine/plugins/adware.py @@ -36,7 +36,7 @@ def hex_string(self, data): def parse(self): return self.__parse_asn1(self.data) - def __parse_asn1(self, data): + def __parse_asn1(self, data, deep=0): ret = [] d = data @@ -45,7 +45,9 @@ def __parse_asn1(self, data): t, l, d1, off = self.get_asn1_data(d) if self.is_constructed(t): - ret.append(self.__parse_asn1(d1)) + deep += 1 + ret.append(self.__parse_asn1(d1, deep)) + deep -= 1 else: x1 = self.hex_string(d1) ttype = t & 0x1f @@ -58,6 +60,9 @@ def __parse_asn1(self, data): else: ret.append(x1) + if deep ==0: + break + d = d[off+l:] return ret From ddc2168cfcad7eccddf22ed802ceb2131a27ccdf Mon Sep 17 00:00:00 2001 From: hanul93 Date: Mon, 5 Mar 2018 10:37:57 +0900 Subject: [PATCH 37/47] Added PDB Patterns --- Engine/plugins/emalware.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Engine/plugins/emalware.py b/Engine/plugins/emalware.py index 8d30e65..564ffab 100644 --- a/Engine/plugins/emalware.py +++ b/Engine/plugins/emalware.py @@ -139,9 +139,15 @@ def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 # case 3. pdb를 이용해서 악성코드 검사 if 'PDB_Name' in ff['pe']: - if ff['pe']['PDB_Name'].find(':\\pz_git\\bin\\') != -1: - vname = kavutil.normal_vname('AdWare.Win32.Sokuxuan.gen') - return True, vname, 0, kernel.INFECTED + pdb_sigs = { + ':\\pz_git\\bin\\': 'AdWare.Win32.Sokuxuan.gen', + ':\\CODE\\vitruvian\\': 'AdWare.Win32.Vitruvian.gen', + } + + for pat in pdb_sigs.keys(): + if ff['pe']['PDB_Name'].find(pat) != -1: + vname = kavutil.normal_vname(pdb_sigs[pat]) + return True, vname, 0, kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: From b95f02ec33d6866497fd1ff793fbf7608a634601 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Tue, 6 Mar 2018 08:58:22 +0900 Subject: [PATCH 38/47] Improved the NSIS parsing speed up --- Engine/plugins/nsis.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Engine/plugins/nsis.py b/Engine/plugins/nsis.py index 1ab2f33..6c1c0b8 100644 --- a/Engine/plugins/nsis.py +++ b/Engine/plugins/nsis.py @@ -377,11 +377,17 @@ def __processentries(self): off = self.nh.entries for i in range(self.nh.entries_num): - nr = StructNsisRecord() - memmove(addressof(nr), self.header_data[off:], sizeof(nr)) - off += sizeof(nr) + # nr = StructNsisRecord() + # memmove(addressof(nr), self.header_data[off:], sizeof(nr)) + # off += sizeof(nr) # 28Byte + + val = self.header_data[off:off + 4] + + # if nr.which == 20: # EW_EXTRACTFILE + if val == '\x14\x00\x00\x00': # EW_EXTRACTFILE + nr = StructNsisRecord() + memmove(addressof(nr), self.header_data[off:], sizeof(nr)) - if nr.which == 20: # EW_EXTRACTFILE dt = '' try: ft_dec = struct.unpack('>Q', struct.pack('>ll', nr.parm4, nr.parm3))[0] @@ -397,7 +403,11 @@ def __processentries(self): file_offset = nr.parm2 self.files[file_name] = (file_offset, dt, nr.which) - elif nr.which == 62: # EW_WRITEUNINSTALLER + # elif nr.which == 62: # EW_WRITEUNINSTALLER + elif val == '\x3e\x00\x00\x00': # EW_WRITEUNINSTALLER + nr = StructNsisRecord() + memmove(addressof(nr), self.header_data[off:], sizeof(nr)) + file_name = self.__get_string(nr.parm0).replace('\\', '/') file_offset = nr.parm1 @@ -408,6 +418,8 @@ def __processentries(self): self.files[file_name] = (file_offset, '', nr.which) + off += 28 + def parse(self): if self.header_data: # 이미 분석되었다면 분석하지 않는다. return self.success From cafe306d9e5a636b4a29fde6eab01daedee604db Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:28:31 +0900 Subject: [PATCH 39/47] Modified cab module to run only on Windows --- Engine/plugins/cab.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Engine/plugins/cab.py b/Engine/plugins/cab.py index 3375b70..259e2c1 100644 --- a/Engine/plugins/cab.py +++ b/Engine/plugins/cab.py @@ -42,9 +42,15 @@ from cStringIO import StringIO except ImportError: from io import StringIO -from ctypes import * -from ctypes.wintypes import BOOL -from functools import wraps + +try: + from ctypes import * + from ctypes.wintypes import BOOL + from functools import wraps + + LOAD_WINTYPES = True +except ImportError: + LOAD_WINTYPES = False import sys import kernel @@ -739,6 +745,10 @@ class KavMain: # --------------------------------------------------------------------- def init(self, plugins_path, verbose=False): # 플러그인 엔진 초기화 self.handle = {} + + if not LOAD_WINTYPES: + return -1 + return 0 # 플러그인 엔진 초기화 성공 # --------------------------------------------------------------------- From 24d24e372dc9209ee053b6e58f9f1d7785081000 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:29:31 +0900 Subject: [PATCH 40/47] Fixed types (c_ulong -> c_uint) --- Engine/plugins/macro.py | 6 +++--- Engine/plugins/nsis.py | 6 +++--- Engine/plugins/pe.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Engine/plugins/macro.py b/Engine/plugins/macro.py index ca482bc..3606aec 100644 --- a/Engine/plugins/macro.py +++ b/Engine/plugins/macro.py @@ -34,15 +34,15 @@ # ------------------------------------------------------------------------- BYTE = c_ubyte WORD = c_ushort -DWORD = c_ulong +DWORD = c_uint FLOAT = c_float LPBYTE = POINTER(c_ubyte) LPTSTR = POINTER(c_char) HANDLE = c_void_p PVOID = c_void_p LPVOID = c_void_p -UINT_PTR = c_ulong -SIZE_T = c_ulong +UINT_PTR = c_uint +SIZE_T = c_uint class MCD(Structure): diff --git a/Engine/plugins/nsis.py b/Engine/plugins/nsis.py index 6c1c0b8..0906eca 100644 --- a/Engine/plugins/nsis.py +++ b/Engine/plugins/nsis.py @@ -28,15 +28,15 @@ BYTE = c_ubyte WORD = c_ushort LONG = c_long -DWORD = c_ulong +DWORD = c_uint FLOAT = c_float LPBYTE = POINTER(c_ubyte) LPTSTR = POINTER(c_char) HANDLE = c_void_p PVOID = c_void_p LPVOID = c_void_p -UINT_PTR = c_ulong -SIZE_T = c_ulong +UINT_PTR = c_uint +SIZE_T = c_uint class StructNsisHeader(Structure): _pack_ = 1 diff --git a/Engine/plugins/pe.py b/Engine/plugins/pe.py index 6e13293..d4ce75b 100644 --- a/Engine/plugins/pe.py +++ b/Engine/plugins/pe.py @@ -12,15 +12,15 @@ BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort -DWORD = ctypes.c_ulong +DWORD = ctypes.c_uint FLOAT = ctypes.c_float LPBYTE = ctypes.POINTER(ctypes.c_ubyte) LPTSTR = ctypes.POINTER(ctypes.c_char) HANDLE = ctypes.c_void_p PVOID = ctypes.c_void_p LPVOID = ctypes.c_void_p -UINT_PTR = ctypes.c_ulong -SIZE_T = ctypes.c_ulong +UINT_PTR = ctypes.c_uint +SIZE_T = ctypes.c_uint class DOS_HEADER(ctypes.Structure): From 35c31e84955bfdbc79f67390f620df8bb8357a87 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:33:00 +0900 Subject: [PATCH 41/47] Added color mode on Linux/Mac --- Engine/k2.py | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index 0d51838..c397716 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -61,30 +61,20 @@ # ------------------------------------------------------------------------- # 콘솔에 색깔 출력을 위한 클래스 및 함수들 # ------------------------------------------------------------------------- -FOREGROUND_BLACK = 0x0000 -FOREGROUND_BLUE = 0x0001 -FOREGROUND_GREEN = 0x0002 -FOREGROUND_CYAN = 0x0003 -FOREGROUND_RED = 0x0004 -FOREGROUND_MAGENTA = 0x0005 -FOREGROUND_YELLOW = 0x0006 -FOREGROUND_GREY = 0x0007 -FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified. - -BACKGROUND_BLACK = 0x0000 -BACKGROUND_BLUE = 0x0010 -BACKGROUND_GREEN = 0x0020 -BACKGROUND_CYAN = 0x0030 -BACKGROUND_RED = 0x0040 -BACKGROUND_MAGENTA = 0x0050 -BACKGROUND_YELLOW = 0x0060 -BACKGROUND_GREY = 0x0070 -BACKGROUND_INTENSITY = 0x0080 # background color is intensified. - NOCOLOR = False # 색깔 옵션값 if os.name == 'nt': + FOREGROUND_BLACK = 0x0000 + FOREGROUND_BLUE = 0x0001 + FOREGROUND_GREEN = 0x0002 + FOREGROUND_CYAN = 0x0003 + FOREGROUND_RED = 0x0004 + FOREGROUND_MAGENTA = 0x0005 + FOREGROUND_YELLOW = 0x0006 + FOREGROUND_GREY = 0x0007 + FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified. + from ctypes import windll, Structure, c_short, c_ushort, byref SHORT = c_short @@ -148,8 +138,25 @@ def cprint(msg, color): except IOError: pass else: + FOREGROUND_BLACK = 0x0000 + FOREGROUND_RED = 0x0001 + FOREGROUND_GREEN = 0x0002 + FOREGROUND_YELLOW = 0x0003 + FOREGROUND_BLUE = 0x0004 + FOREGROUND_MAGENTA = 0x0005 + FOREGROUND_CYAN = 0x0006 + FOREGROUND_GREY = 0x0007 + FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified. + + COLOR_RESET = '\033[0m' # Text Reset + def cprint(msg, color): - sys.stdout.write(msg) + if color & FOREGROUND_INTENSITY == FOREGROUND_INTENSITY: + color &= 0x7 + str_color = '\033[0;%2Xm' % (0x90 + color) + else: + str_color = '\033[0;%2Xm' % (0x30 + color) + sys.stdout.write(str_color + msg + COLOR_RESET) sys.stdout.flush() From a564d362ea260d88e111f159aed75eab618d2142 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:35:52 +0900 Subject: [PATCH 42/47] Modified k2.exe file update only on Windows --- Engine/k2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index c397716..e42192c 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -434,7 +434,7 @@ def update_kicomav(path): if filename != 'k2.exe': download_file(url, filename, path, gz=True, fnhook=hook) - if is_k2_exe_update: + if os.name == 'nt' and is_k2_exe_update: k2temp_path = download_file_k2(url, 'k2.exe', path, gz=True, fnhook=hook) # 업데이트 완료 메시지 출력 @@ -446,7 +446,7 @@ def update_kicomav(path): os.remove(os.path.join(path, 'update.cfg')) # k2.exe의 경우 최종 업데이트 프로그램 실행 - if is_k2_exe_update: + if os.name == 'nt' and is_k2_exe_update: os.spawnv(os.P_NOWAIT, k2temp_path, (k2temp_path, 'k2', path)) except KeyboardInterrupt: cprint('\n[', FOREGROUND_GREY) From 83d52fa50d75d95cb08a22cf74aafa5205aec210 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:37:31 +0900 Subject: [PATCH 43/47] Added exception of update process --- Engine/k2.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Engine/k2.py b/Engine/k2.py index e42192c..afdf76c 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -452,6 +452,10 @@ def update_kicomav(path): cprint('\n[', FOREGROUND_GREY) cprint('Update Stop', FOREGROUND_GREY | FOREGROUND_INTENSITY) cprint(']\n', FOREGROUND_GREY) + except: + cprint('\n[', FOREGROUND_GREY) + cprint('Update faild', FOREGROUND_RED | FOREGROUND_INTENSITY) + cprint(']\n', FOREGROUND_GREY) # 업데이트 진행율 표시 From 9d542a8839e55f6aecabc72652c6a18c94149f14 Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 09:38:56 +0900 Subject: [PATCH 44/47] Modified exception of kmd file import --- Engine/k2.py | 21 ++++++++++----------- Engine/kavcore/k2engine.py | 9 ++++++++- Engine/kavcore/k2kmdfile.py | 15 +++++++++------ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index afdf76c..b576f43 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -909,15 +909,13 @@ def quarantine_callback(filename, is_success): # ------------------------------------------------------------------------- def import_error_callback(module_name): global PLUGIN_ERROR + global g_options - if not PLUGIN_ERROR: - PLUGIN_ERROR = True - print - - if kavcore.k2const.K2DEBUG: - print_error('Invalid plugin: \'%s.py\'' % module_name) - else: - print_error('Invalid plugin: \'%s.kmd\'' % module_name) + if g_options.opt_debug: + if not PLUGIN_ERROR: + PLUGIN_ERROR = True + print + print_error('Invalid plugin: \'%s\'' % module_name) # ------------------------------------------------------------------------- @@ -1032,7 +1030,7 @@ def main(): # 플러그인 엔진 설정 plugins_path = os.path.join(k2_pwd, 'plugins') - if not k2.set_plugins(plugins_path): + if not k2.set_plugins(plugins_path, import_error_callback): print print_error('KICOM Anti-Virus Engine set_plugins') return 0 @@ -1050,8 +1048,9 @@ def main(): print_error('KICOM Anti-Virus Engine init') return 0 - if PLUGIN_ERROR: # 로딩 실패한 플러그인 엔진과 엔진 버전을 구분하기 위해 사용 - print + if options.opt_debug: + if PLUGIN_ERROR: # 로딩 실패한 플러그인 엔진과 엔진 버전을 구분하기 위해 사용 + print # 엔진 버전을 출력 c = kav.get_version() diff --git a/Engine/kavcore/k2engine.py b/Engine/kavcore/k2engine.py index ddd6f68..73a4f57 100644 --- a/Engine/kavcore/k2engine.py +++ b/Engine/kavcore/k2engine.py @@ -77,7 +77,7 @@ def __del__(self): # 인자값 : plugins_path - 플러그인 엔진 경로 # 리턴값 : 성공 여부 # --------------------------------------------------------------------- - def set_plugins(self, plugins_path): + def set_plugins(self, plugins_path, callback_fn=None): # 플러그인 경로를 저장한다. self.plugins_path = plugins_path @@ -122,6 +122,9 @@ def set_plugins(self, plugins_path): # 메모리 로딩에 성공한 KMD에서 플러그 엔진의 시간 값 읽기 # 최신 업데이트 날짜가 된다. self.__get_last_kmd_build_time(k) + else: # 메모리 로딩 실패 + if isinstance(callback_fn, types.FunctionType): + callback_fn(name) except IOError: pass except k2kmdfile.KMDFormatError: # 다른키로 암호호화 한 엔진은 무시 @@ -909,6 +912,10 @@ def __scan_file(self, file_struct, fileformat): except KeyboardInterrupt: raise KeyboardInterrupt except: + if k2const.K2DEBUG: + import traceback + print traceback.format_exc() + raw_input('>>') self.result['IO_errors'] += 1 # 파일 I/O 오류 발생 수 if mm: diff --git a/Engine/kavcore/k2kmdfile.py b/Engine/kavcore/k2kmdfile.py index 453e103..1518451 100644 --- a/Engine/kavcore/k2kmdfile.py +++ b/Engine/kavcore/k2kmdfile.py @@ -326,11 +326,14 @@ def __get_md5(self): # --------------------------------------------------------------------- def load(mod_name, buf): if buf[:4] == '03F30D0A'.decode('hex'): # puc 시그너처가 존재하는가? - code = marshal.loads(buf[8:]) # pyc에서 파이썬 코드를 로딩한다. - module = imp.new_module(mod_name) # 새로운 모듈 생성한다. - exec (code, module.__dict__) # pyc 파이썬 코드와 모듈을 연결한다. - sys.modules[mod_name] = module # 전역에서 사용가능하게 등록한다. - - return module + try: + code = marshal.loads(buf[8:]) # pyc에서 파이썬 코드를 로딩한다. + module = imp.new_module(mod_name) # 새로운 모듈 생성한다. + exec (code, module.__dict__) # pyc 파이썬 코드와 모듈을 연결한다. + sys.modules[mod_name] = module # 전역에서 사용가능하게 등록한다. + + return module + except: + return None else: return None From 4c616f5399baae40da5e1a7da8382dafd5f6e28a Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 11:25:50 +0900 Subject: [PATCH 45/47] Modified version --- Engine/k2.py | 37 ++++--------------------------------- Engine/kavcore/__init__.py | 2 +- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/Engine/k2.py b/Engine/k2.py index b576f43..ff71ac3 100644 --- a/Engine/k2.py +++ b/Engine/k2.py @@ -47,8 +47,8 @@ # ------------------------------------------------------------------------- # 주요 상수 # ------------------------------------------------------------------------- -KAV_VERSION = '0.29' -KAV_BUILDDATE = 'Jan 25 2018' +KAV_VERSION = '0.30' +KAV_BUILDDATE = 'May 07 2018' KAV_LASTYEAR = KAV_BUILDDATE[len(KAV_BUILDDATE)-4:] g_options = None # 옵션 @@ -422,6 +422,7 @@ def update_kicomav(path): try: url = 'https://raw.githubusercontent.com/hanul93/kicomav-db/master/update_v3/' # 서버 주소를 나중에 바꿔야 한다. + # url = 'http://127.0.0.1:8011/' # 서버 주소를 나중에 바꿔야 한다. # 업데이트해야 할 파일 목록을 구한다. down_list = get_download_list(url, path) @@ -705,7 +706,7 @@ def scan_callback(ret_value): # 정상일 경우에는 /<...> path명에 의해 중복 발생 가능성 있음 # 그래서 중복을 출력하지 않도록 조정함 - if message == 'ok': + if message == 'ok': d_prev = display_scan_result.get('Prev', {}) if d_prev == {}: d_prev['disp_name'] = disp_name @@ -857,36 +858,6 @@ def update_callback(ret_file_info, is_success): display_update_result = disp_name -''' -def update_callback1(ret_file_info): - global g_infp_path - - if g_options.opt_move: # 격리 옵션이 설정되었나? - # 마스터 파일만 격리 조치하면 됨 - if ret_file_info.is_modify(): # 격리를 위해 가짜로 수정된것 처럼 처리 - disp_name = ret_file_info.get_master_filename() - try: - shutil.move(disp_name, g_infp_path) - message = 'quarantined' - message_color = FOREGROUND_GREEN | FOREGROUND_INTENSITY - except shutil.Error: - message = 'quarantine failed' - message_color = FOREGROUND_RED | FOREGROUND_INTENSITY - - display_line(disp_name, message, message_color) - log_print('%s\t%s\n' % (disp_name, message)) - else: - if ret_file_info.is_modify(): # 수정되었다면 결과 출력 - disp_name = ret_file_info.get_filename() - - message = 'updated' - message_color = FOREGROUND_GREEN | FOREGROUND_INTENSITY - - display_line(disp_name, message, message_color) - log_print('%s\t%s\n' % (disp_name, message)) -''' - - # ------------------------------------------------------------------------- # quarantine 콜백 함수 # ------------------------------------------------------------------------- diff --git a/Engine/kavcore/__init__.py b/Engine/kavcore/__init__.py index 7a241cc..9959142 100644 --- a/Engine/kavcore/__init__.py +++ b/Engine/kavcore/__init__.py @@ -2,4 +2,4 @@ # Author: Kei Choi(hanul93@gmail.com) -__version__ = '0.29' +__version__ = '0.30' From 71c63b342e5b3342b1ff962133e02cfa7fe4011c Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 11:29:11 +0900 Subject: [PATCH 46/47] Changed log filed --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e00a5d8..cb70383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +## v0.30 (May 07, 2018) + +* **Engine :** + * k2engine: Changed WindowsError exception handling to OSError exception handling + * k2engine: Modified Checking for malicious code to stop immediately via Ctrl+C + * k2file: Changed WindowsError exception handling to OSError exception handling + * k2file: Moved the path of the temporary folder + +* **Plugins Modules :** + * adware: Modified the data processing byte number in the ASN.1 parser + * cryptolib: Supported crc32 + * dde: Changed malware pattern + * hwp: Added Exploit.JS.Agent.gen check function + * kavutil: Added malicious code pattern handling function of virus type + * kavutil: Fixed the error handling part of malicious code pattern number + * macro: Supported 32/64bit + * nsis: Improved decompression speed + * ole: Added CVE-2012-0158 pattern + * ole: Fixed infinite loop error during parsing + * pe: Supported 32/64bit + * pe: Added error handling for invalid resource size + * rtf: Added objdata extraction function + * rtf: Changed malicious code patterns + * upx: Fixed infinite loop error + * ve: New support (scan for malware of virus types) + +* **Command Line Interface :** + * k2: Added color mode in Linux/Mac + * k2: Fixed an error when updating k2.exe from a folder where k2.exe does not exist (# 1455) + * k2: Fixed do not download k2.exe on platforms other than windows + + ## v0.29 (Jan 08, 2018) * **Engine :** From a2d9c25e71f8e915275994d744211511f437968a Mon Sep 17 00:00:00 2001 From: hanul93 Date: Wed, 7 Mar 2018 11:33:11 +0900 Subject: [PATCH 47/47] Modified README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 287b971..73009c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# KicomAV v0.29 +# KicomAV v0.30 [![License](https://img.shields.io/badge/license-gpl2-blue.svg)](LICENSE) ![Platform](https://img.shields.io/badge/platform-windows-lightgrey.svg) @@ -38,7 +38,7 @@ C:\kicomav\Release> python k2.py [path] [options] ``` C:\kicomav\Release> python k2.py ------------------------------------------------------------ -KICOM Anti-Virus II (for WIN32) Ver 0.29 (Jan 08 2018) +KICOM Anti-Virus II (for WIN32) Ver 0.30 (May 07 2018) Copyright (C) 1995-2018 Kei Choi. All rights reserved. ------------------------------------------------------------ @@ -71,7 +71,7 @@ C:\kicomav\Release> _ ``` C:\kicomav\Release>k2.py --update ------------------------------------------------------------ -KICOM Anti-Virus II (for WIN32) Ver 0.29 (Jan 08 2018) +KICOM Anti-Virus II (for WIN32) Ver 0.30 (May 07 2018) Copyright (C) 1995-2018 Kei Choi. All rights reserved. ------------------------------------------------------------ @@ -90,11 +90,11 @@ C:\kicomav\Release> _ ``` C:\kicomav\Release> python k2.py . ------------------------------------------------------------ -KICOM Anti-Virus II (for WIN32) Ver 0.29 (Jan 08 2018) +KICOM Anti-Virus II (for WIN32) Ver 0.30 (May 07 2018) Copyright (C) 1995-2018 Kei Choi. All rights reserved. ------------------------------------------------------------ -Last updated Thu Jan 8 07:50:42 2018 UTC -Signature number: 1,675 +Last updated Wed Mar 7 00:14:58 2018 UTC +Signature number: 2,052 C:\kicomav\Relea ... 08ecba90d0cd778 infected : Trojan-Ransom.Win32.Snocry.cxu C:\kicomav\Release\ ... 218e8a8d7eb93df1003 infected : Trojan.Win32.Agent.icgh @@ -119,11 +119,11 @@ C:\kicomav\Release> _ ``` C:\kicomav\Release> python k2.py sample\test.zip -r -I ------------------------------------------------------------ -KICOM Anti-Virus II (for WIN32) Ver 0.29 (Jan 08 2018) +KICOM Anti-Virus II (for WIN32) Ver 0.30 (May 07 2018) Copyright (C) 1995-2018 Kei Choi. All rights reserved. ------------------------------------------------------------ -Last updated Thu Jan 8 07:50:42 2018 UTC -Signature number: 1,675 +Last updated Wed Mar 7 00:14:58 2018 UTC +Signature number: 2,052 C:\kicomav\Release\sample\test.zip ok C:\kicomav\Relea ... .zip (dummy.txt) infected : Dummy-Test-File (not a virus) @@ -148,11 +148,11 @@ C:\kicomav\Release> _ ``` C:\kicomav\Release> python k2.py -V ------------------------------------------------------------ -KICOM Anti-Virus II (for WIN32) Ver 0.29 (Jan 08 2018) +KICOM Anti-Virus II (for WIN32) Ver 0.30 (May 07 2018) Copyright (C) 1995-2018 Kei Choi. All rights reserved. ------------------------------------------------------------ -Last updated Thu Jan 8 07:50:42 2018 UTC -Signature number: 1,675 +Last updated Wed Mar 7 00:14:58 2018 UTC +Signature number: 2,052 Dummy-Test-File (not a virus) [dummy.kmd] EICAR-Test-File (not a virus) [eicar.kmd]