diff --git a/Image/16.png b/Image/16.png new file mode 100644 index 0000000..9ae6bcc Binary files /dev/null and b/Image/16.png differ diff --git a/Image/17.png b/Image/17.png new file mode 100644 index 0000000..4f89f95 Binary files /dev/null and b/Image/17.png differ diff --git a/Server/log.py b/Server/log.py index c3239e5..63d8356 100644 --- a/Server/log.py +++ b/Server/log.py @@ -70,6 +70,53 @@ def match_threat(process: process.Process, log, log_type): return had_threat, is_ioa, hit_name, hit_score +def update_process_threat_status(current_process: process.Process, host, had_threat): + if current_process is not None: + # if current_process.path.find("f.exe") != -1: + # print(log) + if current_process.chain.risk_score >= config.MAX_THREAT_SCORE: + if had_threat == global_vars.THREAT_TYPE_PROCESS: + current_process.chain.update_process_tree() + threat = sql.select_threat_by_chain_id( + host, current_process.chain.hash, global_vars.THREAT_TYPE_PROCESS + ) + if len(threat) == 0: + process_info: process.Process = None + + if len(current_process.chain.process_list) > 1: + process_info = current_process.chain.process_list[1] + else: + process_info = current_process + info_save_data = { + "path": process_info.path, + "hash": process_info.md5, + "params": process_info.params, + "user": process_info.user, + "create_time": process_info.time, + } + sql.push_threat_log( + host, + current_process.chain.risk_score, + json.dumps(current_process.chain.operationlist), + json.dumps(current_process.chain.attck_hit_list), + current_process.chain.hash, + current_process.chain.get_json(), + global_vars.THREAT_TYPE_PROCESS, + json.dumps(info_save_data), + ) + else: + sql.update_threat_log( + host, + current_process.chain.risk_score, + json.dumps(current_process.chain.operationlist), + json.dumps(current_process.chain.attck_hit_list), + current_process.chain.hash, + current_process.chain.get_json(), + global_vars.THREAT_TYPE_PROCESS, + current_process.chain.active == False, + ) + + def process_log(host, json_log, raw_log): log = json_log["data"] had_threat = global_vars.THREAT_TYPE_NONE @@ -178,51 +225,7 @@ def process_log(host, json_log, raw_log): ) if had_threat == global_vars.THREAT_TYPE_NONE: had_threat = had_threat_plugin - - if current_process is not None: - # if current_process.path.find("f.exe") != -1: - # print(log) - if current_process.chain.risk_score >= config.MAX_THREAT_SCORE: - if had_threat == global_vars.THREAT_TYPE_PROCESS: - current_process.chain.update_process_tree() - threat = sql.select_threat_by_chain_id( - host, current_process.chain.hash, global_vars.THREAT_TYPE_PROCESS - ) - if len(threat) == 0: - process_info: process.Process = None - - if len(current_process.chain.process_list) > 1: - process_info = current_process.chain.process_list[1] - else: - process_info = current_process - info_save_data = { - "path": process_info.path, - "hash": process_info.md5, - "params": process_info.params, - "user": process_info.user, - "create_time": process_info.time, - } - sql.push_threat_log( - host, - current_process.chain.risk_score, - json.dumps(current_process.chain.operationlist), - json.dumps(current_process.chain.attck_hit_list), - current_process.chain.hash, - current_process.chain.get_json(), - global_vars.THREAT_TYPE_PROCESS, - json.dumps(info_save_data), - ) - else: - sql.update_threat_log( - host, - current_process.chain.risk_score, - json.dumps(current_process.chain.operationlist), - json.dumps(current_process.chain.attck_hit_list), - current_process.chain.hash, - current_process.chain.get_json(), - global_vars.THREAT_TYPE_PROCESS, - current_process.chain.active == False, - ) + update_process_threat_status(current_process, host, had_threat) parent_pid = 0 target_pid = 0 self_hash = "" diff --git a/Server/plugins/ioc_opswat/opswat.py b/Server/plugins/ioc_opswat/opswat.py new file mode 100644 index 0000000..636594b --- /dev/null +++ b/Server/plugins/ioc_opswat/opswat.py @@ -0,0 +1,349 @@ +import log +import requests +import global_vars +import process +import hash_white_list +from threading import Thread +# 引入sqlalchemy中相关模块 +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import time +from sqlalchemy import create_engine, MetaData, Table + +STATUS_CLEAN = -1 +STATUS_UNK = 0 +STATUS_VIRUS = 1 + +# 自己去https://metadefender.opswat.com/注册一个申请一个免费的api +# 我这边的api有每日最大使用限制!!!!!!用的人多了很有可能会被封掉 +rm_plugs_config = { + "enable": True, + "author": "huoji", + "description": "opswat ioc检测扩展插件", + "version": "0.0.1", + # !自己去https://metadefender.opswat.com/注册一个申请一个免费的api! + # !自己去https://metadefender.opswat.com/注册一个申请一个免费的api! + # !自己去https://metadefender.opswat.com/注册一个申请一个免费的api! + "apikey": "010d4868aef799750e2828fdf17a4d98", +} +g_engine = None +g_opswat_cache_hashes_table = None +g_opswat_cache_hashes_ins = None +g_opswat_cache_ip_addr_table = None +g_opswat_cache_ip_addr_ins = None +g_sql_base = declarative_base() +g_check_hashes_list = {} +g_check_ip_list = {} + + +class opswat_cache_hashes(g_sql_base): + __tablename__ = "opswat_cache_hashs" + # 定义各字段 + id = Column(Integer, primary_key=True) + # 主机ip + host = Column(String) + # 进程路径 + path = Column(String) + # hash + hash = Column(String) + # 时间戳 + timestamp = Column(Integer) + # 信息 -1绿色 0 未知 1病毒 + status = Column(Integer) + + def __str__(self): + return self.id + + +class opswat_cache_ip_addr(g_sql_base): + __tablename__ = "opswat_cache_ip_addr" + # 定义各字段 + id = Column(Integer, primary_key=True) + # 主机ip + host = Column(String) + # 进程路径 + path = Column(String) + # ip_addr + ip_addr = Column(String) + # 时间戳 + timestamp = Column(Integer) + # 信息 -1绿色 0 未知 1病毒 + status = Column(Integer) + + def __str__(self): + return self.id + + +def search_ip_in_opswat(ip_addr): + request_obj = requests.Session() + request_obj.trust_env = False + url = "https://api.metadefender.com/v4/ip/" + ip_addr + headers = { + "apikey": rm_plugs_config['apikey'], + } + status = STATUS_UNK + try: + response = request_obj.get( + url, headers=headers, timeout=30, verify=True) + if response.status_code == 200: + json_data = response.json() + if 'lookup_results' in json_data: + if json_data['lookup_results']['detected_by'] >= 1: + status = STATUS_VIRUS + else: + status = STATUS_CLEAN + except: + pass + return status + + +def search_hash_in_opswat(hash): + request_obj = requests.Session() + request_obj.trust_env = False + url = "https://api.metadefender.com/v4/hash/" + hash + headers = { + "apikey": rm_plugs_config['apikey'], + } + status = STATUS_UNK + try: + response = request_obj.get( + url, headers=headers, timeout=30, verify=True) + if response.status_code == 200: + json_data = response.json() + if 'scan_all_result_i' in json_data['scan_results']: + if json_data['scan_results']['total_detected_avs'] > 5: + status = STATUS_VIRUS + else: + status = STATUS_CLEAN + except: + pass + return status + + +def async_call(fn): + def wrapper(*args, **kwargs): + Thread(target=fn, args=args, kwargs=kwargs).start() + + return wrapper + + +def query_hash(pHash): + global g_opswat_cache_hashes_table + sql_session = sessionmaker(bind=g_engine) + hash_info = sql_session().query( + g_opswat_cache_hashes_table).filter_by(hash=pHash).first() + sql_session().close() + if hash_info is None: + return False, None + last_time = hash_info[4] + status = hash_info[5] + is_need_update = False + # 10 day + if time.time() - last_time > 864000: + is_need_update = True + return is_need_update, status + + +def query_ipaddr(pIp): + global g_opswat_cache_ip_addr_table + sql_session = sessionmaker(bind=g_engine) + ip_info = sql_session().query( + g_opswat_cache_ip_addr_table).filter_by(ip_addr=pIp).first() + sql_session().close() + if ip_info is None: + return False, None + last_time = ip_info[4] + status = ip_info[5] + is_need_update = False + # 10 day + if time.time() - last_time > 864000: + is_need_update = True + return is_need_update, status + + +def update_ip_addr(ip_addr, net_status): + global g_opswat_cache_ip_addr_table + global g_engine + conn = g_engine.connect() + update = ( + g_opswat_cache_ip_addr_table.update() + .values(status=net_status, + timestamp=int(round(time.time() * 1000))) + .where(g_opswat_cache_ip_addr_table.c.ip_addr == ip_addr) + ) + result = conn.execute(update) + conn.close() + return result + + +def update_hash(hash, new_status): + global g_opswat_cache_hashes_table + global g_engine + conn = g_engine.connect() + update = ( + g_opswat_cache_hashes_table.update() + .values( + status=new_status, + timestamp=int(round(time.time() * 1000)) + ) + .where( + g_opswat_cache_hashes_table.columns.hash == hash + ) + ) + result = conn.execute(update) + conn.close() + return result + + +def push_ip_addr(host, path, ip_addr, status): + global g_opswat_cache_ip_addr_table + global g_engine + conn = g_engine.connect() + insert = g_opswat_cache_ip_addr_table.insert().values( + host=host, + path=path, + ip_addr=ip_addr, + status=status, + timestamp=int(round(time.time() * 1000)) + ) + result = conn.execute(insert) + conn.close() + return result + + +def push_hash( + host, + path, + hash, + status +): + global g_engine + global g_opswat_cache_hashes_table + global g_opswat_cache_hashes_ins + ins = g_opswat_cache_hashes_ins.values( + host=host, + path=path, + hash=hash, + status=status, + timestamp=int(round(time.time() * 1000)) + ) + # 连接引擎 + conn = g_engine.connect() + # 执行语句 + result = conn.execute(ins) + conn.close() + # print(raw_json) + return result + + +@async_call +def asnyc_check_ip(current_process: process.Process, host, ip): + global g_check_ip_list + if ip in g_check_ip_list and g_check_ip_list[ip] != -2: + return g_check_ip_list[ip] + g_check_ip_list[ip] = STATUS_UNK + cache_need_update, cache_status = query_ipaddr(ip) + if cache_need_update or cache_status is None: + create_one = False + if cache_status is None: + create_one = True + cache_status = search_ip_in_opswat(ip) + if create_one: + push_ip_addr(host, current_process.path, ip, cache_status) + else: + push_ip_addr(ip, cache_status) + + if cache_status == STATUS_VIRUS: + current_process.set_score(666, "恶意网络链接IP:{}".format(ip)) + log.update_process_threat_status( + current_process, host, global_vars.THREAT_TYPE_PROCESS) + elif cache_status == STATUS_UNK: + # crowdstrike: 这个我熟 + current_process.set_score(10, "低信誉ip链接:{}".format(ip)) + log.update_process_threat_status( + current_process, host, global_vars.THREAT_TYPE_PROCESS) + g_check_ip_list[ip] = cache_status + + +@async_call +def asnyc_check_domian(current_process: process.Process, host, domain): + pass + + +@async_call +def asnyc_check_hash(current_process: process.Process, host): + global g_check_hashes_list + hash = current_process.md5 + if hash in g_check_hashes_list and g_check_hashes_list[hash] != -2: + return g_check_hashes_list[hash] + g_check_hashes_list[hash] = STATUS_UNK + cache_need_update, cache_status = query_hash(hash) + if cache_need_update or cache_status is None: + create_one = False + if cache_status is None: + create_one = True + cache_status = search_hash_in_opswat(hash) + if create_one: + push_hash(host, current_process.path, hash, cache_status) + else: + update_hash(hash, cache_status) + + if cache_status == STATUS_VIRUS: + current_process.set_score(666, "恶意软件") + log.update_process_threat_status( + current_process, host, global_vars.THREAT_TYPE_PROCESS) + elif cache_status == STATUS_UNK: + # crowdstrike: 这个我熟 + current_process.set_score(10, "低信誉文件") + log.update_process_threat_status( + current_process, host, global_vars.THREAT_TYPE_PROCESS) + g_check_hashes_list[hash] = cache_status + + +def rule_new_process_create(current_process: process.Process, host, raw_log_data, json_log_data): + global g_check_hashes_list + if rm_plugs_config['apikey'] != "" is not None and hash_white_list.check_in_while_list(current_process) == False: + g_check_hashes_list[current_process.md5] = -2 + asnyc_check_hash(current_process, host) + return global_vars.THREAT_TYPE_NONE + + +def rule_new_process_action(current_process: process.Process, host, raw_log_data, json_log_data): + global g_check_ip_list + if rm_plugs_config['apikey'] != "" is not None and json_log_data['action'] == 'networkconnect' and hash_white_list.check_in_while_list(current_process) == False: + # print('network connect{}'.format( + # json_log_data['data']['destinationip'])) + g_check_ip_list[json_log_data['data']['destinationip']] = -2 + asnyc_check_ip(current_process, host, + json_log_data['data']['destinationip']) + return global_vars.THREAT_TYPE_NONE + + +def rule_init(): + pass + + +def plugin_init(): + global g_engine + global g_metadata + global g_sql_base + global g_opswat_cache_hashes_table + global g_opswat_cache_hashes_ins + global g_opswat_cache_ip_addr_table + global g_opswat_cache_ip_addr_ins + print('opswat ioc检测扩展插件 2022/9/23 by huoji') + + if rm_plugs_config['apikey'] != "": + g_engine = create_engine( + "sqlite:///plugin_opswat_cache.db?check_same_thread=False", echo=False) + g_sql_base.metadata.create_all(g_engine) + g_metadata = MetaData(g_engine) + g_opswat_cache_hashes_table = Table( + "opswat_cache_hashs", g_metadata, autoload=True) + g_opswat_cache_hashes_ins = g_opswat_cache_hashes_table.insert() + g_opswat_cache_ip_addr_table = Table( + "opswat_cache_ip_addr", g_metadata, autoload=True) + g_opswat_cache_ip_addr_ins = g_opswat_cache_ip_addr_table.insert() + else: + print('opswat ioc检测扩展插件未配置apikey,自己去metadefender.opswat.com申请一个!') diff --git a/Server/plugins/otx_alienvault/otx.py b/Server/plugins/otx_alienvault/otx.py deleted file mode 100644 index 4b59379..0000000 --- a/Server/plugins/otx_alienvault/otx.py +++ /dev/null @@ -1,25 +0,0 @@ -import global_vars -import process - -rm_plugs_config = { - "enable": True, - "author": "huoji", - "description": "otx alienvault ioc检测扩展插件", - "version": "0.0.1" -} - - -def rule_new_process_create(current_process: process.Process, host, raw_log_data, json_log_data): - return global_vars.THREAT_TYPE_NONE - - -def rule_new_process_action(current_process: process.Process, host, raw_log_data, json_log_data): - return global_vars.THREAT_TYPE_NONE - - -def rule_init(): - pass - - -def plugin_init(): - print('otx alienvault ioc检测扩展插件 2022/9/23 by huoji') diff --git a/Server/rules/py/attck/action.py b/Server/rules/py/attck/action.py index 674370d..acc5c16 100644 --- a/Server/rules/py/attck/action.py +++ b/Server/rules/py/attck/action.py @@ -30,6 +30,7 @@ rule = [ 'action == "createremotethread"', ], 'attck_hit':['T1055'], + 'score': 30, 'name': 'Process Injection' }, { diff --git a/Server/sql.py b/Server/sql.py index 13d6bc0..17c71f4 100644 --- a/Server/sql.py +++ b/Server/sql.py @@ -147,6 +147,7 @@ def delete_white_list(pHash): delete(g_hash_white_list_table).where( g_hash_white_list_table.columns.hash == pHash) ) + conn.close() return result @@ -159,6 +160,7 @@ def push_white_list(pPath, pHash, pReason): conn = g_engine.connect() # 执行语句 result = conn.execute(ins) + conn.close() return result @@ -228,6 +230,7 @@ def push_process_raw( conn = g_engine.connect() # 执行语句 result = conn.execute(ins) + conn.close() return result @@ -303,6 +306,7 @@ def update_threat_log( ) ) result = conn.execute(update) + conn.close() return result @@ -316,6 +320,7 @@ def handle_threat_log(threat_id, handle_type): .where(g_threat_table.columns.id == int(threat_id)) ) result = conn.execute(update) + conn.close() return result @@ -327,6 +332,7 @@ def delete_threat(threat_id): delete(g_threat_table).where( g_threat_table.columns.id == int(threat_id)) ) + conn.close() return result @@ -430,5 +436,6 @@ def push_threat_log( conn = g_engine.connect() # 执行语句 result = conn.execute(ins) + conn.close() # print(raw_json) return result diff --git a/Server/webserver.py b/Server/webserver.py index c9df79a..8f4b4c3 100644 --- a/Server/webserver.py +++ b/Server/webserver.py @@ -1,15 +1,15 @@ +import logging import hash_white_list import json from flask import Flask from flask import request import sql import log -import rule import config from flask import Flask, render_template, request import plugin -import logging import html +import rule import statistics app = Flask( __name__, diff --git a/readme.md b/readme.md index 1245b59..dac5f26 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,9 @@ https://key08.com/index.php/2022/08/09/1505.html 请牢记,RmEye自身定位是轻量级威胁检出工具 ### 最新新闻 +2022/9/23: +国庆节更新,增加ip与hash的ioc插件,目前Rmeye有能力对ip和hash进行标注,使用时务必换成自己的apikey,其他请看下面的ioc部分 + 2022/9/22: 增加仪表盘,可视化展示检测结果 @@ -50,6 +53,9 @@ https://github.com/RoomaSec/RmEye/blob/main/doc_day0_rule.md 增加uac提权检测插件`uac_bypass_detect`,但是受限于sysmon,没有办法获取RPC信息,因此只能检测一部分的UAC提权行为.并且有误报,请酌情考虑 ### 检出截图 +IOC(2022/10/1更新): +![image](Image/16.png) +![image](Image/17.png) 威胁列表(2022/9/20更新): ![image](Image/1.png) 仪表盘(2022/9/22更新): @@ -140,6 +146,9 @@ sysmon /uninstall ``` 即可干净卫生的卸载掉RmEye +### IOC +目前RmEye使用的是`https://metadefender.opswat.com/`的免费IOC,目前的apikey仅用于测试,自己部署的时候请务必打开`plugins/ioc_opswat/opswat.py`把`"apikey": "010d4868aef799750e2828fdf17a4d98"`换成你自己的,否做会不安全(比如其他人能查得到你的请求记录)/有使用量限制(100次一天).所以务必换成你自己注册的账号.这个IOC源是免费的而且好用的,比OTX好用 + ### 规则相关的问题 1. 规则目前仅120条,很多攻击面没有覆盖,其他规则请访问《社区》 2. 规则目前只支持rule_engine与yara的规则,其中yara的规则支持是以插件的形式支持