def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pyc' in fileformat: if self.verbose: print '-' * 79 kavutil.vprint('Engine') kavutil.vprint(None, 'Engine', 'pyz.kmd') mm = filehandle # String 추출 if len(mm): if self.verbose: print kavutil.vprint('String') for match in self.p_string.finditer(mm): find_str = match.group() find_str_off = match.start() # 중요 문자열 시작전에 해당 문자열의 길이가 존재함 x = kavutil.get_uint32(mm, find_str_off - 4) if len(find_str) < x: continue buf = find_str[:x] fsize = len(buf) if self.verbose: fmd5 = cryptolib.md5(buf) kavutil.vprint(None, fmd5, '%3d : %s' % (fsize, buf)) if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): fmd5 = cryptolib.md5(buf) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def scan(self, filehandle, filename): try: mm = filehandle size = os.path.getsize(filename) if size == 68: fmd5 = cryptolib.md5(mm[:68]) if fmd5 == '44d88612fea8a8f36de82e1278abb02f': return True, 'EICAR-Test-File (not a virus)', 0 except IOError: pass return False, '', -1
def scan(self, filehandle, filename): try: mm = filehandle size = os.path.getsize(filename) # 검사 대상 파일의 크기 계산 if size == 68: # 크기가 일치한다면 MD5 해시 계산 fmd5 = cryptolib.md5(mm[:68]) # 파일에서 얻은 해시 값과 EICAR Test 악성코드의 해시 값이 일치하는가? if fmd5 == '44d88612fea8a8f36de82e1278abb02f': return True, 'EICAR-Test-File (not a virus)', 0 except IOError: pass # 악성코드 발견 못하면 return False, '', -1
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle size = os.path.getsize(filename) # 검사 대상 파일 크기를 구한다. if size == 68: # EICAR Test 악성코드의 크기와 일치하는가? # 크기가 일치한다면 MD5 해시 계산 fmd5 = cryptolib.md5(mm[:68]) # 파일에서 얻은 해시 값과 EICAR Test 악성코드의 해시 값이 일치하는가? if fmd5 == '44d88612fea8a8f36de82e1278abb02f': return True, 'EICAR-Test-File (not a virus)', 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def parse(self): mm = self.mm pe_format = {'PE_Position': 0, 'EntryPoint': 0, 'SectionNumber': 0, 'Sections': None, 'EntryPointRaw': 0, 'FileAlignment': 0} try: if mm[0:2] != 'MZ': # MZ로 시작하나? raise ValueError dos_header = DOS_HEADER() ctypes.memmove(ctypes.addressof(dos_header), mm[0:], ctypes.sizeof(dos_header)) # PE 표식자 위치 알아내기 pe_pos = dos_header.e_lfanew # PE 인가? if mm[pe_pos:pe_pos + 4] != 'PE\x00\x00': raise ValueError pe_format['PE_Position'] = pe_pos # File Header 읽기 file_header = FILE_HEADER() file_header_size = ctypes.sizeof(file_header) # file_header_size : 0x14 ctypes.memmove(ctypes.addressof(file_header), mm[pe_pos + 4:], file_header_size) # Optional Header 읽기 optional_header = OPTIONAL_HEADER() optional_header_size = ctypes.sizeof(optional_header) ctypes.memmove(ctypes.addressof(optional_header), mm[pe_pos + 4 + file_header_size:], optional_header_size) # Optional Header의 Magic ID? if optional_header.Magic != 0x10b: raise ValueError # Entry Point 구하기 pe_ep = optional_header.AddressOfEntryPoint pe_format['EntryPoint'] = pe_ep # Image Base 구하기 pe_img = optional_header.ImageBase pe_format['ImageBase'] = pe_img # File Alignment 구하기 self.pe_file_align = optional_header.FileAlignment pe_format['FileAlignment'] = self.pe_file_align # Section 개수 구하기 section_num = file_header.NumberOfSections pe_format['SectionNumber'] = section_num # Optional Header 크기 구하기 opthdr_size = file_header.SizeOfOptionalHeader pe_format['OptionalHederSize'] = opthdr_size # Data Directory 읽기 data_directory_size = ctypes.sizeof(DATA_DIRECTORY()) # data_directory_size : 8 num_data_directory = (opthdr_size - optional_header_size) / data_directory_size off_data_directory = pe_pos + 4 + file_header_size + optional_header_size for i in range(num_data_directory): dx = DATA_DIRECTORY() ctypes.memmove(ctypes.addressof(dx), mm[off_data_directory + (i * data_directory_size):], data_directory_size) self.data_directories.append(dx) # 섹션 시작 위치 section_pos = pe_pos + 4 + file_header_size + opthdr_size # 모든 섹션 정보 추출 for i in range(section_num): section = {} section_header = SECTION_HEADER() section_header_size = ctypes.sizeof(section_header) # section_header_size : 0x28 s = section_pos + (section_header_size * i) ctypes.memmove(ctypes.addressof(section_header), mm[s:], section_header_size) sec_name = ctypes.cast(section_header.Name, ctypes.c_char_p) section['Name'] = sec_name.value.replace('\x00', '') section['VirtualSize'] = section_header.Misc_VirtualSize section['RVA'] = section_header.VirtualAddress section['SizeRawData'] = section_header.SizeOfRawData section['PointerRawData'] = section_header.PointerToRawData section['Characteristics'] = section_header.Characteristics self.sections.append(section) pe_format['Sections'] = self.sections # EntryPoint의 파일에서의 위치 구하기 ep_raw, sec_idx = self.rva_to_off(pe_ep) pe_format['EntryPointRaw'] = ep_raw # EP의 Raw 위치 pe_format['EntryPoint_in_Section'] = sec_idx # EP가 포함된 섹션 # 리소스 분석 try: rsrc_rva = self.data_directories[image_directory_entry.RESOURCE].VirtualAddress # 리소스 위치(RVA) rsrc_size = self.data_directories[image_directory_entry.RESOURCE].Size # 리소스 크기 except IndexError: rsrc_rva = 0 rsrc_size = 0 if rsrc_rva: # 리소스가 존재한가? try: rsrc_off, rsrc_idx = self.rva_to_off(rsrc_rva) # 리소스 위치 변환 if rsrc_off > self.filesize: raise ValueError t_size = self.sections[rsrc_idx]['SizeRawData'] if not (len(mm[rsrc_off:rsrc_off + rsrc_size]) == rsrc_size or \ len(mm[rsrc_off:rsrc_off + t_size]) == t_size): # 충분한 리소스가 존재하지 않음 raise ValueError # Type 체크 num_type_name = kavutil.get_uint16(mm, rsrc_off+0xC) num_type_id = kavutil.get_uint16(mm, rsrc_off + 0xE) for i in range(num_type_name + num_type_id): type_id = kavutil.get_uint32(mm, rsrc_off + 0x10 + (i*8)) name_id_off = kavutil.get_uint32(mm, rsrc_off + 0x14 + (i * 8)) # Type이 사용자가 정의한 이름 or RCDATA? if type_id & 0x80000000 == 0x80000000 or type_id == 0xA or type_id == 0: if type_id & 0x80000000 == 0x80000000: # 사용자가 정의한 이름 추출 string_off = (type_id & 0x7FFFFFFF) + rsrc_off len_name = kavutil.get_uint16(mm, string_off) rsrc_type_name = mm[string_off + 2:string_off + 2 + (len_name * 2):2] elif type_id == 0xA: rsrc_type_name = 'RCDATA' else: rsrc_type_name = '%d' % type_id # 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) for j in range(num_name_id_name + num_name_id_id): name_id_id = kavutil.get_uint32(mm, name_id_off + 0x10 + (j * 8)) language_off = kavutil.get_uint32(mm, name_id_off + 0x14 + (j * 8)) # 리소스 영역의 최종 이름 생성 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 else: string_name = rsrc_type_name + '/' + hex(name_id_id).upper()[2:] # 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) for k in range(num_language_name + num_language_id): # language_id = kavutil.get_uint32(mm, language_off + 0x10 + (k * 8)) data_entry_off = kavutil.get_uint32(mm, language_off + 0x14 + (k * 8)) data_entry_off = (data_entry_off & 0x7FFFFFFF) + rsrc_off 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: pe_format['Resource_UserData'][string_name] = (data_off, data_size) else: pe_format['Resource_UserData'] = {string_name: (data_off, data_size)} except (struct.error, ValueError) as e: pass # if 'Resource_UserData' in pe_format: # print pe_format['Resource_UserData'] # Import API 분석 try: imp_rva = self.data_directories[image_directory_entry.IMPORT].VirtualAddress # Import API 위치(RVA) imp_size = self.data_directories[image_directory_entry.IMPORT].Size # Import API 크기 except IndexError: imp_rva = 0 imp_size = 0 if imp_rva: # Import API 존재 imp_api = {} # print 'IMP : %08X' % imp_rva imp_off = self.rva_to_off(imp_rva)[0] # print hex(imp_off), imp_size imp_data = mm[imp_off:imp_off+imp_size] if len(imp_data) == imp_size: for i in range(imp_size / 0x14): # DLL 정보 크기가 0x14 try: dll_rva = kavutil.get_uint32(imp_data, (i*0x14)+0xC) api_rva = kavutil.get_uint32(imp_data, (i * 0x14)) bo = 2 if api_rva == 0: api_rva = kavutil.get_uint32(imp_data, (i*0x14)+0x10) bo = 0 # print hex(api_rva) if dll_rva == 0: # DLL 정보가 없음 break t_off = self.rva_to_off(dll_rva)[0] dll_name = p_str.search(mm[t_off:t_off+0x20]).group() # print '[+]', dll_name imp_api[dll_name] = [] t_off = self.rva_to_off(api_rva)[0] while True: try: api_name_rva = kavutil.get_uint32(mm, t_off) except struct.error: break if api_name_rva & 0x80000000 == 0x80000000: # Odinal API t_off += 4 continue if api_name_rva == 0: break t = self.rva_to_off(api_name_rva)[0] # print hex(t_off), hex(t) api_name = p_str.search(mm[t+bo:t+bo+0x20]).group() # print ' ', api_name imp_api[dll_name].append(api_name) t_off += 4 except struct.error: pass # end if pe_format['Import_API'] = imp_api # 디지털 인증서 분석 try: cert_off = self.data_directories[image_directory_entry.SECURITY].VirtualAddress # 유일하게 RVA가 아닌 오프셋 cert_size = self.data_directories[image_directory_entry.SECURITY].Size # 디지털 인증서 크기 except IndexError: cert_off = 0 cert_size = 0 if cert_off: # 디지털 인증서 존재 if cert_off + cert_size <= len(mm[:]): # UPack의 경우 이상한 값이 셋팅 됨 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') kavutil.vprint(None, 'Engine', 'pe.kmd') kavutil.vprint(None, 'File name', os.path.split(self.filename)[-1]) kavutil.vprint(None, 'MD5', cryptolib.md5(mm[:])) print kavutil.vprint('PE') kavutil.vprint(None, 'EntryPoint', '%08X' % pe_format['EntryPoint']) kavutil.vprint(None, 'EntryPoint (Section)', '%d' % pe_format['EntryPoint_in_Section']) # 섹션 보기 if section_num: print kavutil.vprint('Section Header') print ' %-8s %-8s %-8s %-8s %-8s %-8s' % ('Name', 'VOFF', 'VSIZE', 'FOFF', 'FSIZE', 'EXEC') print ' ' + ('-' * (9*6 - 1)) for s in self.sections: print ' %-8s %08X %08X %08X %08X %-05s' % (s['Name'], s['RVA'], s['VirtualSize'], s['PointerRawData'], s['SizeRawData'], s['Characteristics'] & 0x20000000 == 0x20000000) if section_num: print kavutil.vprint('Section MD5') print ' %-8s %-8s %-32s' % ('Name', 'FSIZE', 'MD5') print ' ' + ('-' * ((9 * 2 - 1)+32)) for s in self.sections: # if s['Characteristics'] & 0x20000000 == 0x20000000: off = s['PointerRawData'] size = s['SizeRawData'] fmd5 = cryptolib.md5(mm[off:off+size]) if size else '-' print ' %-8s %8d %s' % (s['Name'], size, fmd5) print kavutil.vprint('Entry Point (Raw)') 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 repr(pe_format['PDB_Name']) print except (ValueError, struct.error) as e: return None return pe_format
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 : %08x\n' % (cs_size[i], int(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
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: ff = fileformat['ff_pe'] # print ff for section in ff['pe']['Sections']: if (section['Characteristics'] & 0x20000000) == 0x20000000: # 실행 속성? # print section['Name'], hex(section['SizeRawData']) fsize = section['SizeRawData'] if kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = section['PointerRawData'] fmd5 = cryptolib.md5(mm[foff:foff+fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] if len(ff['elf']['Sections']): for section in ff['elf']['Sections']: if (section['Type'] & 0x1) == 0x1 and (section['Flag'] & 0x4) == 0x4: # 프로그램 데이터이면서 실행 속성? # print section['Name'], section['Size'], section['Offset'] fsize = section['Size'] if kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = section['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED elif len(ff['elf']['ProgramHeaders']): for ph in ff['elf']['ProgramHeaders']: if (ph['Type'] & 0x1) == 0x1 and (ph['Flag'] & 0x1) == 0x1: fsize = ph['Size'] if kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = ph['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED # Mirai 변종 진단 ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': fsize = section['Size'] foff = section['Offset'] if self.p_linux_mirai.match(mm[foff:foff+fsize]): return True, 'Backdoor.Linux.Mirai.gen', 0, kernel.SUSPECT ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': vstring = [] foff = section['Offset'] ret = self.aho_mirai_a.search(mm[foff:foff + 0x200]) for n in ret[:len(self.mirai_a_strings)]: vstring.append(n[1]) # print vstring # print len(set(vstring)), len(self.mirai_a_strings) if set(vstring) == set(self.mirai_a_strings): return True, 'Backdoor.Linux.Mirai.a.gen', 0, kernel.SUSPECT # NSIS 같은 설치 프로그램의 경우 첨부 영역에 존재하는데.. # 디컴파일하지 않고 오리지널 이미지 원본을 탐지하도록 했음.. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] buf = mm[foff:] fmd5 = cryptolib.md5(buf) # 첨부 위치부터 끝까지 vname = kavutil.handle_pattern_md5.scan('emalware', len(buf), fmd5) if vname: return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: ff = fileformat['ff_pe'] # case 1 : 섹션 전체를 hash로 검사 for idx, section in enumerate(ff['pe']['Sections']): # if (section['Characteristics'] & 0x20000000) == 0x20000000: # 실행 속성? # print section['Name'], hex(section['SizeRawData']) fsize = section['SizeRawData'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['PointerRawData'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # case 2. 마지막 섹션에 실행 파일 존재 if len(ff['pe']['Sections']): # 마지막 섹션 sec = ff['pe']['Sections'][-1] off = sec['PointerRawData'] size = sec['SizeRawData'] # 실행 파일이 존재하는가? exe_offs = [ m.start() for m in re.finditer('MZ', mm[off:off + size]) ] for exe_pos in exe_offs: fsize = 0x1d5 if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): fmd5 = cryptolib.md5(mm[off + exe_pos:off + exe_pos + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: # return True, vname, 0, kernel.INFECTED idx = len(ff['pe']['Sections']) - 1 vname = kavutil.normal_vname(vname) return True, vname, (0x80000000 + idx), kernel.INFECTED # case 3. pdb를 이용해서 악성코드 검사 if 'PDB_Name' in ff['pe']: pdb_sigs = { ':\\pz_git\\bin\\': '<n>AdWare.Win32.Sokuxuan.gen', ':\\CODE\\vitruvian\\': '<n>AdWare.Win32.Vitruvian.gen', '\\bin\\Release\\WebSparkle.': '<n>AdWare.MSIL.BrowseFox.gen', ':\\TeamCity\\BuildAgent1\\work\\': '<n>WebToolbar.Win32.Agent.avi', } 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 # case 4. Worm.Win32.Allaple.gen 검사 ep_off = ff['pe']['EntryPointRaw'] data = mm[ep_off:ep_off + 0x80] if self.p_allaple.search(data): return True, 'Worm.Win32.Allaple.gen', 0, kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] if len(ff['elf']['Sections']): for section in ff['elf']['Sections']: if (section['Type'] & 0x1) == 0x1 and ( section['Flag'] & 0x4) == 0x4: # 프로그램 데이터이면서 실행 속성? # print section['Name'], section['Size'], section['Offset'] fsize = section['Size'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED elif len(ff['elf']['ProgramHeaders']): for ph in ff['elf']['ProgramHeaders']: if (ph['Type'] & 0x1) == 0x1 and (ph['Flag'] & 0x1) == 0x1: fsize = ph['Size'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = ph['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # Mirai 변종 진단 ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': fsize = section['Size'] foff = section['Offset'] if self.p_linux_mirai.match(mm[foff:foff+fsize]): return True, 'Backdoor.Linux.Mirai.gen', 0, kernel.SUSPECT ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': vstring = [] foff = section['Offset'] ret = self.aho_mirai_a.search(mm[foff:foff + 0x200]) for n in ret[:len(self.mirai_a_strings)]: vstring.append(n[1]) # print vstring # print len(set(vstring)), len(self.mirai_a_strings) if set(vstring) == set(self.mirai_a_strings): return True, 'Backdoor.Linux.Mirai.a.gen', 0, kernel.SUSPECT # NSIS 같은 설치 프로그램의 경우 첨부 영역에 존재하는데.. # 디컴파일하지 않고 오리지널 이미지 원본을 탐지하도록 했음.. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] buf = mm[foff:] fsize = len(buf) if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): fmd5 = cryptolib.md5(buf) # 첨부 위치부터 끝까지 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: ff = fileformat['ff_pe'] # print ff for section in ff['pe']['Sections']: if (section['Characteristics'] & 0x20000000) == 0x20000000: # 실행 속성? # print section['Name'], hex(section['SizeRawData']) fsize = section['SizeRawData'] if kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['PointerRawData'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] if len(ff['elf']['Sections']): for section in ff['elf']['Sections']: if (section['Type'] & 0x1) == 0x1 and ( section['Flag'] & 0x4) == 0x4: # 프로그램 데이터이면서 실행 속성? # print section['Name'], section['Size'], section['Offset'] fsize = section['Size'] if kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED elif len(ff['elf']['ProgramHeaders']): for ph in ff['elf']['ProgramHeaders']: if (ph['Type'] & 0x1) == 0x1 and (ph['Flag'] & 0x1) == 0x1: fsize = ph['Size'] if kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = ph['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: return True, vname, 0, kernel.INFECTED # Mirai 변종 진단 ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': fsize = section['Size'] foff = section['Offset'] if self.p_linux_mirai.match(mm[foff:foff+fsize]): return True, 'Backdoor.Linux.Mirai.gen', 0, kernel.SUSPECT ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': vstring = [] foff = section['Offset'] ret = self.aho_mirai_a.search(mm[foff:foff + 0x200]) for n in ret[:len(self.mirai_a_strings)]: vstring.append(n[1]) # print vstring # print len(set(vstring)), len(self.mirai_a_strings) if set(vstring) == set(self.mirai_a_strings): return True, 'Backdoor.Linux.Mirai.a.gen', 0, kernel.SUSPECT # NSIS 같은 설치 프로그램의 경우 첨부 영역에 존재하는데.. # 디컴파일하지 않고 오리지널 이미지 원본을 탐지하도록 했음.. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] buf = mm[foff:] fmd5 = cryptolib.md5(buf) # 첨부 위치부터 끝까지 vname = kavutil.handle_pattern_md5.scan( 'emalware', len(buf), fmd5) if vname: return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def parse(self): mm = self.mm pe_format = { 'PE_Position': 0, 'EntryPoint': 0, 'SectionNumber': 0, 'Sections': None, 'EntryPointRaw': 0, 'FileAlignment': 0 } try: if mm[0:2] != 'MZ': # MZ로 시작하나? raise ValueError # PE 표식자 위치 알아내기 pe_pos = kavutil.get_uint32(mm, 0x3C) # PE 인가? if mm[pe_pos:pe_pos + 4] != 'PE\x00\x00': raise ValueError pe_format['PE_Position'] = pe_pos # Optional Header의 Magic ID? if mm[pe_pos + 0x18:pe_pos + 0x18 + 2] != '\x0B\x01': raise ValueError # Entry Point 구하기 pe_ep = kavutil.get_uint32(mm, pe_pos + 0x28) pe_format['EntryPoint'] = pe_ep # Image Base 구하기 pe_img = kavutil.get_uint32(mm, pe_pos + 0x34) pe_format['ImageBase'] = pe_img # File Alignment 구하기 self.pe_file_align = kavutil.get_uint32(mm, pe_pos + 0x3C) pe_format['FileAlignment'] = self.pe_file_align # Section 개수 구하기 section_num = kavutil.get_uint16(mm, pe_pos + 0x6) pe_format['SectionNumber'] = section_num # Optional Header 크기 구하기 opthdr_size = kavutil.get_uint16(mm, pe_pos + 0x14) pe_format['OptionalHederSize'] = opthdr_size # t섹션 시작 위치 section_pos = pe_pos + 0x18 + opthdr_size # 모든 섹션 정보 추출 for i in range(section_num): section = {} s = section_pos + (0x28 * i) section['Name'] = mm[s:s + 8].replace('\x00', '') section['VirtualSize'] = kavutil.get_uint32(mm, s + 8) section['RVA'] = kavutil.get_uint32(mm, s + 12) section['SizeRawData'] = kavutil.get_uint32(mm, s + 16) section['PointerRawData'] = kavutil.get_uint32(mm, s + 20) section['Characteristics'] = kavutil.get_uint32(mm, s + 36) self.sections.append(section) pe_format['Sections'] = self.sections # EntryPoint의 파일에서의 위치 구하기 ep_raw, sec_idx = self.rva_to_off(pe_ep) pe_format['EntryPointRaw'] = ep_raw # EP의 Raw 위치 pe_format['EntryPoint_in_Section'] = sec_idx # EP가 포함된 섹션 # 리소스 분석 rsrc_rva = kavutil.get_uint32(mm, pe_pos + 0x88) # 리소스 위치(RVA) rsrc_size = kavutil.get_uint32(mm, pe_pos + 0x8C) # 리소스 크기 if rsrc_rva: # 리소스가 존재한가? try: rsrc_off, _ = self.rva_to_off(rsrc_rva) # 리소스 위치 변환 # Type 체크 num_type_name = kavutil.get_uint16(mm, rsrc_off + 0xC) num_type_id = kavutil.get_uint16(mm, rsrc_off + 0xE) for i in range(num_type_name + num_type_id): type_id = kavutil.get_uint32(mm, rsrc_off + 0x10 + (i * 8)) name_id_off = kavutil.get_uint32( mm, rsrc_off + 0x14 + (i * 8)) # Type이 사용자가 정의한 이름 or RCDATA? if type_id & 0x80000000 == 0x80000000 or type_id == 0xA: if type_id & 0x80000000 == 0x80000000: # 사용자가 정의한 이름 추출 string_off = (type_id & 0x7FFFFFFF) + rsrc_off len_name = kavutil.get_uint16(mm, string_off) rsrc_type_name = mm[string_off + 2:string_off + 2 + (len_name * 2):2] else: rsrc_type_name = 'RCDATA' # Name ID name_id_off = (name_id_off & 0x7FFFFFFF) + rsrc_off num_name_id_name = kavutil.get_uint16( mm, name_id_off + 0xC) num_name_id_id = kavutil.get_uint16( mm, name_id_off + 0xE) for j in range(num_name_id_name + num_name_id_id): name_id_id = kavutil.get_uint32( mm, name_id_off + 0x10 + (j * 8)) language_off = kavutil.get_uint32( mm, name_id_off + 0x14 + (j * 8)) # 리소스 영역의 최종 이름 생성 if name_id_id & 0x80000000 == 0x80000000: string_off = (name_id_id & 0x7FFFFFFF) + rsrc_off 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 else: string_name = rsrc_type_name + '/' + hex( name_id_id).upper()[2:] # Language language_off = (language_off & 0x7FFFFFFF) + rsrc_off num_language_name = kavutil.get_uint16( mm, language_off + 0xC) num_language_id = kavutil.get_uint16( mm, language_off + 0xE) for k in range(num_language_name + num_language_id): # language_id = kavutil.get_uint32(mm, language_off + 0x10 + (k * 8)) data_entry_off = kavutil.get_uint32( mm, language_off + 0x14 + (k * 8)) data_entry_off = (data_entry_off & 0x7FFFFFFF) + rsrc_off data_rva = kavutil.get_uint32( mm, data_entry_off) data_off, _ = self.rva_to_off(data_rva) data_size = kavutil.get_uint32( mm, data_entry_off + 4) if data_size > 8192: # 최소 8K 이상인 리소스만 데이터로 추출 if 'Resource_UserData' in pe_format: pe_format['Resource_UserData'][ string_name] = (data_off, data_size) else: pe_format['Resource_UserData'] = { string_name: (data_off, data_size) } except struct.error: pass # if 'Resource_UserData' in pe_format: # print pe_format['Resource_UserData'] # Import API 분석 imp_rva = kavutil.get_uint32(mm, pe_pos + 0x80) # Import API 위치(RVA) imp_size = kavutil.get_uint32(mm, pe_pos + 0x84) # Import API 크기 if imp_rva: # Import API 존재 imp_api = {} # print 'IMP : %08X' % imp_rva imp_off = self.rva_to_off(imp_rva)[0] # print hex(imp_off), imp_size imp_data = mm[imp_off:imp_off + imp_size] for i in range(imp_size / 0x14): # DLL 정보 크기가 0x14 try: dll_rva = kavutil.get_uint32(imp_data, (i * 0x14) + 0xC) api_rva = kavutil.get_uint32(imp_data, (i * 0x14)) bo = 2 if api_rva == 0: api_rva = kavutil.get_uint32( imp_data, (i * 0x14) + 0x10) bo = 0 # print hex(api_rva) if dll_rva == 0: # DLL 정보가 없음 break t_off = self.rva_to_off(dll_rva)[0] dll_name = p_str.search(mm[t_off:t_off + 0x20]).group() # print '[+]', dll_name imp_api[dll_name] = [] t_off = self.rva_to_off(api_rva)[0] while True: try: api_name_rva = kavutil.get_uint32(mm, t_off) except struct.error: break if api_name_rva & 0x80000000 == 0x80000000: # Odinal API t_off += 4 continue if api_name_rva == 0: break t = self.rva_to_off(api_name_rva)[0] # print hex(t_off), hex(t) api_name = p_str.search(mm[t + bo:t + bo + 0x20]).group() # print ' ', api_name imp_api[dll_name].append(api_name) t_off += 4 except struct.error: pass pe_format['Import_API'] = imp_api # 디지털 인증서 분석 cert_off = kavutil.get_uint32(mm, pe_pos + 0x98) # 디지털 인증서 위치(유일하게 RVA가 아닌 오프셋) cert_size = kavutil.get_uint32(mm, pe_pos + 0x9C) # 디지털 인증서 크기 if cert_off: # 디지털 인증서 존재 if cert_off + cert_size <= len(mm[:]): # UPack의 경우 이상한 값이 셋팅 됨 pe_format['CERTIFICATE_Offset'] = cert_off pe_format['CERTIFICATE_Size'] = cert_size if self.verbose: print '-' * 79 kavutil.vprint('Engine') kavutil.vprint(None, 'Engine', 'pe.kmd') kavutil.vprint(None, 'File name', os.path.split(self.filename)[-1]) kavutil.vprint(None, 'MD5', cryptolib.md5(mm[:])) print kavutil.vprint('PE') kavutil.vprint(None, 'EntryPoint', '%08X' % pe_format['EntryPoint']) kavutil.vprint(None, 'EntryPoint (Section)', '%d' % pe_format['EntryPoint_in_Section']) # 섹션 보기 if section_num: print kavutil.vprint('Section Header') print ' %-8s %-8s %-8s %-8s %-8s %-8s' % ( 'Name', 'VOFF', 'VSIZE', 'FOFF', 'FSIZE', 'EXEC') print ' ' + ('-' * (9 * 6 - 1)) for s in self.sections: print ' %-8s %08X %08X %08X %08X %-05s' % ( s['Name'], s['RVA'], s['VirtualSize'], s['PointerRawData'], s['SizeRawData'], s['Characteristics'] & 0x20000000 == 0x20000000) if section_num: print kavutil.vprint('Section MD5') print ' %-8s %-8s %-32s' % ('Name', 'FSIZE', 'MD5') print ' ' + ('-' * ((9 * 2 - 1) + 32)) for s in self.sections: if s['Characteristics'] & 0x20000000 == 0x20000000: off = s['PointerRawData'] size = s['SizeRawData'] fmd5 = cryptolib.md5(mm[off:off + size]) print ' %-8s %8d %s' % (s['Name'], size, fmd5) print kavutil.vprint('Entry Point (Raw)') print kavutil.HexDump().Buffer(mm[:], pe_format['EntryPointRaw'], 0x80) print except ValueError: return None return pe_format
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: ff = fileformat['ff_pe'] # case 1 : 섹션 전체를 hash로 검사 for idx, section in enumerate(ff['pe']['Sections']): # if (section['Characteristics'] & 0x20000000) == 0x20000000: # 실행 속성? # print section['Name'], hex(section['SizeRawData']) fsize = section['SizeRawData'] if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = section['PointerRawData'] fmd5 = cryptolib.md5(mm[foff:foff+fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # case 2. 마지막 섹션에 실행 파일 존재 if len(ff['pe']['Sections']): # 마지막 섹션 sec = ff['pe']['Sections'][-1] off = sec['PointerRawData'] size = sec['SizeRawData'] # 실행 파일이 존재하는가? exe_offs = [m.start() for m in re.finditer('MZ', mm[off:off+size])] for exe_pos in exe_offs: fsize = 0x1d5 if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): fmd5 = cryptolib.md5(mm[off + exe_pos:off + exe_pos + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: # return True, vname, 0, kernel.INFECTED idx = len(ff['pe']['Sections']) - 1 vname = kavutil.normal_vname(vname) return True, vname, (0x80000000 + idx), kernel.INFECTED # case 3. pdb를 이용해서 악성코드 검사 if 'PDB_Name' in ff['pe']: pdb_sigs = { ':\\pz_git\\bin\\': '<n>AdWare.Win32.Sokuxuan.gen', ':\\CODE\\vitruvian\\': '<n>AdWare.Win32.Vitruvian.gen', '\\bin\\Release\\WebSparkle.': '<n>AdWare.MSIL.BrowseFox.gen', ':\\TeamCity\\BuildAgent1\\work\\': '<n>WebToolbar.Win32.Agent.avi', } 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 # case 4. Worm.Win32.Allaple.gen 검사 ep_off = ff['pe']['EntryPointRaw'] data = mm[ep_off:ep_off+0x80] if self.p_allaple.search(data): return True, 'Worm.Win32.Allaple.gen', 0, kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] if len(ff['elf']['Sections']): for section in ff['elf']['Sections']: if (section['Type'] & 0x1) == 0x1 and (section['Flag'] & 0x4) == 0x4: # 프로그램 데이터이면서 실행 속성? # print section['Name'], section['Size'], section['Offset'] fsize = section['Size'] if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = section['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED elif len(ff['elf']['ProgramHeaders']): for ph in ff['elf']['ProgramHeaders']: if (ph['Type'] & 0x1) == 0x1 and (ph['Flag'] & 0x1) == 0x1: fsize = ph['Size'] if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): foff = ph['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # Mirai 변종 진단 ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': fsize = section['Size'] foff = section['Offset'] if self.p_linux_mirai.match(mm[foff:foff+fsize]): return True, 'Backdoor.Linux.Mirai.gen', 0, kernel.SUSPECT ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': vstring = [] foff = section['Offset'] ret = self.aho_mirai_a.search(mm[foff:foff + 0x200]) for n in ret[:len(self.mirai_a_strings)]: vstring.append(n[1]) # print vstring # print len(set(vstring)), len(self.mirai_a_strings) if set(vstring) == set(self.mirai_a_strings): return True, 'Backdoor.Linux.Mirai.a.gen', 0, kernel.SUSPECT # NSIS 같은 설치 프로그램의 경우 첨부 영역에 존재하는데.. # 디컴파일하지 않고 오리지널 이미지 원본을 탐지하도록 했음.. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] buf = mm[foff:] fsize = len(buf) if fsize and kavutil.handle_pattern_md5.match_size('emalware', fsize): fmd5 = cryptolib.md5(buf) # 첨부 위치부터 끝까지 vname = kavutil.handle_pattern_md5.scan('emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: ff = fileformat['ff_pe'] # case 1 : 섹션 전체를 hash로 검사 for idx, section in enumerate(ff['pe']['Sections']): # if (section['Characteristics'] & 0x20000000) == 0x20000000: # 실행 속성? # print section['Name'], hex(section['SizeRawData']) fsize = section['SizeRawData'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['PointerRawData'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # case 2. 마지막 섹션에 실행 파일 존재 if len(ff['pe']['Sections']): # 마지막 섹션 sec = ff['pe']['Sections'][-1] off = sec['PointerRawData'] size = sec['SizeRawData'] # 실행 파일이 존재하는가? exe_offs = [ m.start() for m in re.finditer('MZ', mm[off:off + size]) ] for exe_pos in exe_offs: fsize = 0x1d5 if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): fmd5 = cryptolib.md5(mm[off + exe_pos:off + exe_pos + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: # return True, vname, 0, kernel.INFECTED idx = len(ff['pe']['Sections']) - 1 vname = kavutil.normal_vname(vname) return True, vname, (0x80000000 + idx), kernel.INFECTED # 미리 분석된 파일 포맷중에 ELF 포맷이 있는가? elif 'ff_elf' in fileformat: ff = fileformat['ff_elf'] if len(ff['elf']['Sections']): for section in ff['elf']['Sections']: if (section['Type'] & 0x1) == 0x1 and ( section['Flag'] & 0x4) == 0x4: # 프로그램 데이터이면서 실행 속성? # print section['Name'], section['Size'], section['Offset'] fsize = section['Size'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = section['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED elif len(ff['elf']['ProgramHeaders']): for ph in ff['elf']['ProgramHeaders']: if (ph['Type'] & 0x1) == 0x1 and (ph['Flag'] & 0x1) == 0x1: fsize = ph['Size'] if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): foff = ph['Offset'] fmd5 = cryptolib.md5(mm[foff:foff + fsize]) # print fsize, fmd5 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED # Mirai 변종 진단 ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': fsize = section['Size'] foff = section['Offset'] if self.p_linux_mirai.match(mm[foff:foff+fsize]): return True, 'Backdoor.Linux.Mirai.gen', 0, kernel.SUSPECT ''' for section in ff['elf']['Sections']: if section['Name'] == '.rodata': vstring = [] foff = section['Offset'] ret = self.aho_mirai_a.search(mm[foff:foff + 0x200]) for n in ret[:len(self.mirai_a_strings)]: vstring.append(n[1]) # print vstring # print len(set(vstring)), len(self.mirai_a_strings) if set(vstring) == set(self.mirai_a_strings): return True, 'Backdoor.Linux.Mirai.a.gen', 0, kernel.SUSPECT # NSIS 같은 설치 프로그램의 경우 첨부 영역에 존재하는데.. # 디컴파일하지 않고 오리지널 이미지 원본을 탐지하도록 했음.. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] buf = mm[foff:] fsize = len(buf) if fsize and kavutil.handle_pattern_md5.match_size( 'emalware', fsize): fmd5 = cryptolib.md5(buf) # 첨부 위치부터 끝까지 vname = kavutil.handle_pattern_md5.scan( 'emalware', fsize, fmd5) if vname: vname = kavutil.normal_vname(vname) return True, vname, 0, kernel.INFECTED except IOError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def feature(self, filehandle, filename, fileformat, filename_ex, malware_id): # Feature 추출 try: mm = filehandle # 미리 분석된 파일 포맷중에 NSIS 포맷이 있는가? # 파일의 전체 영역에 대해 MD5를 구하기 위해 ff_attach를 확인한다. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] # NSIS가 맞나? if mm[foff+4:foff+20] == '\xEF\xBE\xAD\xDENullsoftInst': buf = mm[:] fmd5 = cryptolib.md5(buf).decode('hex') # 파일 전체 MD5 생성 header = 'NSIS' + struct.pack('<L', malware_id) + fmd5 rname = tempfile.mktemp(prefix='ktmp') open(rname, 'wb').write(mm[foff:]) max_len = 0 max_extract_data = '' # 용량이 큰 파일 n = NSIS(rname, False) if n.parse() is False: n.close() return False for name in n.namelist(): data = n.read(name) if data is None: continue data_len = len(data) if max_len < data_len: max_len = data_len max_extract_data = data # Feature 추출 f = kavutil.Feature() data = '' # 1. NSIS 내부 파일 중 용량이 제일 큰 파일을 찾아 엔트로피를 추출한다. data += f.entropy(max_extract_data) # 2. NSIS 헤더 정보를 추출한다. data += n.nsis_header.header_data[:256] # 3. NSIS 헤더의 문자열을 추출 후 2-gram 처리한다. data += f.k_gram(n.nsis_header.header_data, 2) # 4. NSIS 스크립트의 OPcode를 추출한다. t = [0] * 256 off = n.nsis_header.nh.entries for i in range(n.nsis_header.nh.entries_num): nr = StructNsisRecord() memmove(addressof(nr), n.nsis_header.header_data[off:], sizeof(nr)) off += sizeof(nr) if t[nr.which & 0xff] < 0xff: t[nr.which & 0xff] += 1 # Opcode 등장 회수를 누적 data += ''.join(map(chr, t)) n.close() open('nsis.bin', 'ab').write(header + data) # Feature 파일 생성 os.remove(rname) return True except IOError: pass # Feature 추출 실패했음을 리턴한다. return False
def __scan_asn1(self, filehandle, filename, fileformat, filename_ex): mm = filehandle ff = fileformat['ff_pe'] cert_off = ff['pe'].get('CERTIFICATE_Offset', 0) cert_size = ff['pe'].get('CERTIFICATE_Size', 0) if cert_off != 0 and cert_size != 0: if self.verbose: print '-' * 79 kavutil.vprint('Engine') kavutil.vprint(None, 'Engine', 'adware.kmd') # 인증서 추출 cert_data = mm[cert_off:cert_off + cert_size] asn1 = ASN1() asn1.set_data(cert_data[8:]) try: r = asn1.parse() # Signed Data 이면서 버전 정보가 1인가? if r[0][0] == '2A 86 48 86 F7 0D 01 07 02' and r[0][1][0][0] == '01': signeddata = r[0][1][0] certificates = signeddata[3] signerinfo = r[0][1][0][-1] issuer_and_serialnumber = signerinfo[0][1] issuer_serial = issuer_and_serialnumber[1] for cert in certificates: if cert[0][1] == issuer_serial: # 동일한 일련번호 찾기 for x in cert[0][5]: if x[0][0] == '55 04 03': # Common Name signer_name = x[0][1] break else: continue # no break encountered break else: raise IndexError # 일련번호의 길이가 제각각이라 md5 고정길이로 만듬 fmd5 = cryptolib.md5(issuer_serial) fsize = kavutil.get_uint16(fmd5.decode('hex'), 0) if self.verbose: kavutil.vprint('Signer') kavutil.vprint(None, 'Name', signer_name) kavutil.vprint(None, 'Serial Number', issuer_serial) msg = '%d:%s: # %s, %s\n' % (fsize, fmd5, signer_name, cryptolib.sha256(mm)) open('adware.mdb', 'at').write(msg) if fsize and kavutil.handle_pattern_md5.match_size('adware', fsize): vname = kavutil.handle_pattern_md5.scan('adware', fsize, fmd5) if vname: pos = ff['pe'].get('EntryPointRaw', 0) if mm[pos:pos + 4] == '\xff\x25\x00\x20': pf = 'MSIL' else: pf = 'Win32' vname = kavutil.normal_vname(vname, pf) return True, vname, 0, kernel.INFECTED except IndexError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def feature(self, filehandle, filename, fileformat, filename_ex, malware_id): # Feature 추출 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: buf = mm[:] fmd5 = cryptolib.md5(buf).decode('hex') # 파일 전체 MD5 생성 header = 'PE\x00\x00' + struct.pack('<L', malware_id) + fmd5 pe = PE(mm, False, filename) pe_format = pe.parse() if not pe_format: return None pe_off = pe_format['PE_Position'] # pe.DOS_HEADER.e_lfanew ep = pe_format['EntryPoint'] # pe.OPTIONAL_HEADER.AddressOfEntryPoint text_off = 0 text_size = 0 for sec in pe_format['Sections']: # pe.sections: rva = sec['RVA'] # sec.VirtualAddress vsize = sec['VirtualSize'] # sec.Misc_VirtualSize if rva <= ep <= rva + vsize: text_off = sec['PointerRawData'] # sec.PointerToRawData text_size = sec['SizeRawData'] # sec.SizeOfRawData break # Feature 추출 f = kavutil.Feature() data = '' # 1. text 섹션에 대해서 엔트로피를 추출한다. data += f.entropy(mm[text_off:text_off + text_size]) # 2. PE 헤더 정보를 추출한다. data += mm[pe_off + 6:pe_off + 6 + 256] # 3. DATA 섹션 2-gram 추출하기 data_off = 0 data_size = 0 for sec in pe_format['Sections']: # pe.sections: if sec['Characteristics'] & 0x40000040 == 0x40000040: # if DATA and Read data_off = sec['PointerRawData'] # sec.PointerToRawData data_size = sec['SizeRawData'] # sec.SizeOfRawData break data += f.k_gram(mm[data_off:data_off + data_size], 2) # 4. Import API 해시 추가하기 def import_api(l_pe_format): api_hash = set() l_data = '' if 'Import_API' in l_pe_format: imp_api = pe_format['Import_API'] # print imp_api for dll in imp_api.keys(): for api in dll: api_name = ('%s:%s' % (dll, api)).lower() api_hash.add(struct.pack('<H', cryptolib.CRC16().calculate(api_name))) t = list(api_hash) l_data = ''.join(t) if len(l_data) < 256: l_data += '\x00' * (256 - len(l_data)) return l_data[:256] data += import_api(pe_format) open('pe.bin', 'ab').write(header + data) # Feature 파일 생성 return True except IOError: pass # Feature 추출 실패했음을 리턴한다. return False
def __scan_asn1(self, filehandle, filename, fileformat, filename_ex): mm = filehandle ff = fileformat['ff_pe'] cert_off = ff['pe'].get('CERTIFICATE_Offset', 0) cert_size = ff['pe'].get('CERTIFICATE_Size', 0) if cert_off != 0 and cert_size != 0: if self.verbose: print '-' * 79 kavutil.vprint('Engine') kavutil.vprint(None, 'Engine', 'adware.kmd') # 인증서 추출 cert_data = mm[cert_off:cert_off + cert_size] asn1 = ASN1() asn1.set_data(cert_data[8:]) try: r = asn1.parse() # Signed Data 이면서 버전 정보가 1인가? if r[0][0] == '2A 86 48 86 F7 0D 01 07 02' and r[0][1][0][ 0] == '01': signeddata = r[0][1][0] certificates = signeddata[3] signerinfo = r[0][1][0][-1] issuer_and_serialnumber = signerinfo[0][1] issuer_serial = issuer_and_serialnumber[1] for cert in certificates: if cert[0][1] == issuer_serial: # 동일한 일련번호 찾기 for x in cert[0][5]: if x[0][0] == '55 04 03': # Common Name signer_name = x[0][1] break else: continue # no break encountered break else: raise IndexError # 일련번호의 길이가 제각각이라 md5 고정길이로 만듬 fmd5 = cryptolib.md5(issuer_serial) fsize = kavutil.get_uint16(fmd5.decode('hex'), 0) if self.verbose: kavutil.vprint('Signer') kavutil.vprint(None, 'Name', signer_name) kavutil.vprint(None, 'Serial Number', issuer_serial) msg = '%d:%s: # %s, %s\n' % (fsize, fmd5, signer_name, cryptolib.sha256(mm)) open('adware.mdb', 'at').write(msg) if fsize and kavutil.handle_pattern_md5.match_size( 'adware', fsize): vname = kavutil.handle_pattern_md5.scan( 'adware', fsize, fmd5) if vname: pos = ff['pe'].get('EntryPointRaw', 0) if mm[pos:pos + 4] == '\xff\x25\x00\x20': pf = 'MSIL' else: pf = 'Win32' vname = kavutil.normal_vname(vname, pf) return True, vname, 0, kernel.INFECTED except IndexError: pass # 악성코드를 발견하지 못했음을 리턴한다. return False, '', -1, kernel.NOT_FOUND
def feature(self, filehandle, filename, fileformat, filename_ex, malware_id): # Feature 추출 try: mm = filehandle # 미리 분석된 파일 포맷중에 NSIS 포맷이 있는가? # 파일의 전체 영역에 대해 MD5를 구하기 위해 ff_attach를 확인한다. if 'ff_attach' in fileformat: foff = fileformat['ff_attach']['Attached_Pos'] # NSIS가 맞나? if mm[foff + 4:foff + 20] == '\xEF\xBE\xAD\xDENullsoftInst': buf = mm[:] fmd5 = cryptolib.md5(buf).decode('hex') # 파일 전체 MD5 생성 header = 'NSIS' + struct.pack('<L', malware_id) + fmd5 rname = tempfile.mktemp(prefix='ktmp') open(rname, 'wb').write(mm[foff:]) max_len = 0 max_extract_data = '' # 용량이 큰 파일 n = NSIS(rname, False) if n.parse() is False: n.close() return False for name in n.namelist(): data = n.read(name) if data is None: continue data_len = len(data) if max_len < data_len: max_len = data_len max_extract_data = data # Feature 추출 f = kavutil.Feature() data = '' # 1. NSIS 내부 파일 중 용량이 제일 큰 파일을 찾아 엔트로피를 추출한다. data += f.entropy(max_extract_data) # 2. NSIS 헤더 정보를 추출한다. data += n.nsis_header.header_data[:256] # 3. NSIS 헤더의 문자열을 추출 후 2-gram 처리한다. data += f.k_gram(n.nsis_header.header_data, 2) # 4. NSIS 스크립트의 OPcode를 추출한다. t = [0] * 256 off = n.nsis_header.nh.entries for i in range(n.nsis_header.nh.entries_num): nr = StructNsisRecord() memmove(addressof(nr), n.nsis_header.header_data[off:], sizeof(nr)) off += sizeof(nr) if t[nr.which & 0xff] < 0xff: t[nr.which & 0xff] += 1 # Opcode 등장 회수를 누적 data += ''.join(map(chr, t)) n.close() open('nsis.bin', 'ab').write(header + data) # Feature 파일 생성 os.remove(rname) return True except IOError: pass # Feature 추출 실패했음을 리턴한다. return False
def feature(self, filehandle, filename, fileformat, filename_ex, malware_id): # Feature 추출 try: mm = filehandle # 미리 분석된 파일 포맷중에 PE 포맷이 있는가? if 'ff_pe' in fileformat: buf = mm[:] fmd5 = cryptolib.md5(buf).decode('hex') # 파일 전체 MD5 생성 header = 'PE\x00\x00' + struct.pack('<L', malware_id) + fmd5 pe = PE(mm, False, filename) pe_format = pe.parse() if not pe_format: return None pe_off = pe_format['PE_Position'] # pe.DOS_HEADER.e_lfanew ep = pe_format[ 'EntryPoint'] # pe.OPTIONAL_HEADER.AddressOfEntryPoint text_off = 0 text_size = 0 for sec in pe_format['Sections']: # pe.sections: rva = sec['RVA'] # sec.VirtualAddress vsize = sec['VirtualSize'] # sec.Misc_VirtualSize if rva <= ep <= rva + vsize: text_off = sec[ 'PointerRawData'] # sec.PointerToRawData text_size = sec['SizeRawData'] # sec.SizeOfRawData break # Feature 추출 f = kavutil.Feature() data = '' # 1. text 섹션에 대해서 엔트로피를 추출한다. data += f.entropy(mm[text_off:text_off + text_size]) # 2. PE 헤더 정보를 추출한다. data += mm[pe_off + 6:pe_off + 6 + 256] # 3. DATA 섹션 2-gram 추출하기 data_off = 0 data_size = 0 for sec in pe_format['Sections']: # pe.sections: if sec['Characteristics'] & 0x40000040 == 0x40000040: # if DATA and Read data_off = sec[ 'PointerRawData'] # sec.PointerToRawData data_size = sec['SizeRawData'] # sec.SizeOfRawData break data += f.k_gram(mm[data_off:data_off + data_size], 2) # 4. Import API 해시 추가하기 def import_api(l_pe_format): api_hash = set() l_data = '' if 'Import_API' in l_pe_format: imp_api = pe_format['Import_API'] # print imp_api for dll in imp_api.keys(): for api in dll: api_name = ('%s:%s' % (dll, api)).lower() api_hash.add( struct.pack( '<H', cryptolib.CRC16().calculate(api_name))) t = list(api_hash) l_data = ''.join(t) if len(l_data) < 256: l_data += '\x00' * (256 - len(l_data)) return l_data[:256] data += import_api(pe_format) open('pe.bin', 'ab').write(header + data) # Feature 파일 생성 return True except IOError: pass # Feature 추출 실패했음을 리턴한다. return False
def scan(self, filehandle, filename, fileformat, filename_ex): # 악성코드 검사 try: self.flags_off = {} flags = [] mm = filehandle # Virus.Win32.Small.a 검사 ret, vname = self.__scan_virus_win32_small_a(filehandle, fileformat) if ret: return True, vname, 0, kernel.INFECTED # 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 # Attach 영역이 존재하는가? if 'ff_attach' in fileformat: # Flag - 4 : Attach 영역 pos = fileformat['ff_attach']['Attached_Pos'] size = fileformat['ff_attach']['Attached_Size'] if size > 0x80: flags.append([int('0004' + mm[pos:pos+2].encode('hex'), 16), gen_checksums(mm[pos:pos + 0x80])]) self.flags_off[4] = [pos] cs_size = [1, 2, 3, 4, 5, 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 : %08x\n' % (cs_size[i], int(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