26 Commits
v0.0.4 ... main

Author SHA1 Message Date
Huoji's
f11f6445ac Update readme.md 2023-10-25 15:55:45 +08:00
Huoji's
ac1e01bfec Add files via upload 2023-10-25 15:55:28 +08:00
Huoji's
dc60b03bcf Update readme.md 2023-06-27 19:00:22 +08:00
Huoji's
0b85f35184 Merge pull request #5 from chain312/main
完善requirements.txt
2022-10-19 19:35:36 +08:00
chain312
8e94c48e34 完善requirements.txt 2022-10-19 19:34:14 +08:00
Huoji's
ff4f16e109 Merge pull request #4 from chain312/main
新增requirements.txt
2022-10-17 21:37:40 +08:00
chain312
6b2eea6c18 新增requirements.txt 2022-10-17 21:23:25 +08:00
huoji
1885161d67 增加自定义仪表盘
增加自定义仪表盘
2022-10-11 20:12:06 +08:00
aa
7752d9465d 添加调色盘 2022-10-11 19:18:56 +08:00
huoji
f867ebfb33 1
1
2022-10-11 17:53:42 +08:00
huoji
a31398883e 1
1
2022-10-11 16:59:26 +08:00
huoji
534443a475 重新设计了界面 2022-10-11 16:53:51 +08:00
huoji
c8292cf977 修复两处bug
修复两处bug
2022-10-08 16:52:27 +08:00
huoji
0447387079 Update readme.md 2022-09-29 18:50:32 +08:00
huoji
27a22c2074 Update opswat.py 2022-09-29 17:16:04 +08:00
huoji
b3c6b5ae3a 更新ioc插件
国庆更新: 更新ioc插件
2022-09-29 16:53:29 +08:00
huoji
1aece69ad5 给插件增加白名单,预留otx的ioc对接插件
给插件增加白名单,预留otx的ioc对接插件
2022-09-28 13:51:55 +08:00
huoji
d6ca9d7273 微信群替换成知识星球
微信群替换成知识星球
2022-09-27 18:18:24 +08:00
huoji
abaaeff5d9 Update hash_white_list.py 2022-09-23 20:33:43 +08:00
huoji
6c513aeb04 Update plugin.py 2022-09-23 15:25:48 +08:00
huoji
d5b88c7a01 fixed
fixed
2022-09-23 15:21:33 +08:00
huoji
011496349a github
仪表盘增加自动刷新
2022-09-21 20:08:38 +08:00
huoji
e1fb23c112 增加仪表盘
增加仪表盘
2022-09-21 19:58:49 +08:00
huoji
3ddca10161 Update process.py 2022-09-21 15:49:40 +08:00
huoji
643bd9f103 Update sysmon.xml 2022-09-21 15:37:24 +08:00
huoji
9cda67c636 Update sysmon.xml 2022-09-21 15:36:06 +08:00
48 changed files with 1067 additions and 334 deletions

BIN
Image/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
Image/17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
Image/18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

BIN
Image/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
Image/dashboard_new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 94 KiB

BIN
Image/wx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,3 +1,4 @@
import process
import sql
g_white_list = []
g_white_dll_load_list = [
@@ -24,6 +25,22 @@ g_white_dll_load_list = [
]
def check_in_while_list(process: process.Process):
parent_process = process.parent_process
is_white = process.is_white or process.chain.root_process.is_white or process.parent_process.is_white
if is_white == False:
while parent_process:
if parent_process is None:
break
if parent_process.is_white:
is_white = True
break
if parent_process == process.chain.root_process:
break
parent_process = parent_process.parent_process
return is_white
def add_white_list(path, hash, reason):
global g_white_list
if hash in g_white_list:

View File

@@ -14,7 +14,7 @@ LOG_TYPE_PROCESS_ACTION = 2
def update_att_ck(process: process.Process, score, hit_name, attck_t_list):
if process.is_white or process.chain.root_process.is_white or process.parent_process.is_white:
if hash_white_list.check_in_while_list(process):
score = 0
for t in attck_t_list:
process.set_attck(score, t, hit_name)
@@ -24,8 +24,8 @@ def update_att_ck(process: process.Process, score, hit_name, attck_t_list):
def update_threat(process: process.Process, score, rule_hit_name):
had_threat = global_vars.THREAT_TYPE_NONE
if process.is_white or process.chain.root_process.is_white or process.parent_process.is_white:
return had_threat
if hash_white_list.check_in_while_list(process):
score = 0
if score > 0:
# 更新命中的规则
process.set_score(score, rule_hit_name)
@@ -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 = ""

View File

@@ -97,15 +97,15 @@ def dispath_html_menu():
plugin_menu = []
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "html_menu"):
plugin_menu.append(plug_obj.html_menu())
if hasattr(plug_obj, "html_menu"):
plugin_menu.append(plug_obj.html_menu())
return plugin_menu
def dispath_html_draw(name):
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "html_draw"):
if plug_obj.rm_plugs_config['html'] == name:
return plug_obj.html_draw()
if hasattr(plug_obj, "html_draw"):
if plug_obj.rm_plugs_config['html'] == name:
return plug_obj.html_draw()
return 'Access Denied '

View File

@@ -0,0 +1,342 @@
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))
elif cache_status == STATUS_UNK:
# crowdstrike: 这个我熟
current_process.set_score(10, "低信誉ip链接:{}".format(ip))
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, "恶意软件")
elif cache_status == STATUS_UNK:
# crowdstrike: 这个我熟
current_process.set_score(10, "低信誉文件")
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']))
ip_addr = json_log_data['data']['destinationip']
if len(ip_addr) >= 5:
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申请一个!')

View File

@@ -1,6 +1,6 @@
import global_vars
import process
import hash_white_list
rm_plugs_config = {
"enable": True,
"author": "huoji",
@@ -31,7 +31,7 @@ mimikatz_dll_list = [
def rule_new_process_create(current_process: process.Process, host, raw_log_data, json_log_data):
# 服务端提供了一个 plugin_var 变量用于存放当前进程插件的上下文
if current_process.path != 'c:\\windows\\system32\\wbem\\wmic.exe' and current_process.parent_process.path != 'c:\\windows\\system32\\svchost.exe' and current_process.path != 'c:\\windows\\system32\\svchost.exe':
if current_process.path != 'c:\\windows\\system32\\wbem\\wmic.exe' and current_process.parent_process.path != 'c:\\windows\\system32\\svchost.exe' and current_process.path != 'c:\\windows\\system32\\svchost.exe' and hash_white_list.check_in_while_list(current_process) == False:
current_process.plugin_var['mimikatz_matched_num'] = 0
current_process.plugin_var['mimikatz_detected'] = False
return global_vars.THREAT_TYPE_NONE

View File

@@ -1,7 +1,7 @@
import global_vars
import process
#import yara
import hash_white_list
rm_plugs_config = {
"enable": True,
"author": "huoji",
@@ -26,7 +26,7 @@ def rule_new_process_create(current_process: process.Process, host, raw_log_data
if 'uac_flag' not in current_process.chain.root_process.plugin_var:
current_process.chain.root_process.plugin_var['uac_flag'] = integritylevel
if integritylevel > current_process.chain.root_process.plugin_var['uac_flag']:
if integritylevel > current_process.chain.root_process.plugin_var['uac_flag'] and hash_white_list.check_in_while_list(current_process) == False:
print('[uac bypass detect] detect uac bypass in process chain {}'.format(
current_process.path))
current_process.chain.root_process.plugin_var['uac_flag'] = integritylevel

View File

@@ -6,13 +6,6 @@ rule = [
'attck_hit':['T1562.001'],
'name': 'Impair Defenses: Disable or Modify Tools'
},
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe"',
],
'attck_hit':['T1003'],
'name': 'OS Credential Dumping: LSASS Memory'
},
{
'rules': [
'action == "processaccess" and calltrace =~ ".*unknown.*" and not calltrace =~ ".*conpty\.node.*" and not calltrace =~ ".*java\.dll.*" and not calltrace =~ ".*appvisvsubsystems64\.dll.*" and not calltrace =~ ".*twinui\.dll.*" and not calltrace =~ ".*nativeimages.*" and not targetimage == "c:\\windows\\system32\\cmd.exe"',
@@ -37,6 +30,7 @@ rule = [
'action == "createremotethread"',
],
'attck_hit':['T1055'],
'score': 30,
'name': 'Process Injection'
},
{

View File

@@ -1,9 +1,9 @@
rule = [
{
'rules': [
'originalfilename =~ ".*taskill.exe.*"',
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*stop.*"',
'originalfilename =~ ".*sc.exe.*" and commandline =~ ".*config.*" and commandline =~ ".*disabled.*"',
'originalfilename == "taskill.exe"',
'originalfilename == "net.exe" and commandline =~ ".*stop.*"',
'originalfilename == "sc.exe" and commandline =~ ".*config.*" and commandline =~ ".*disabled.*"',
],
'attck_hit':['T1489'],
'score': 30,
@@ -44,7 +44,7 @@ rule = [
},
{
'rules': [
'originalfilename =~ ".*vssadmin.exe.*" and commandline =~ ".*create.*"',
'originalfilename =~ ".*vssadmin.exe" and commandline =~ ".*create.*"',
],
'attck_hit':['T1003.003'],
'score': 30,
@@ -52,7 +52,7 @@ rule = [
},
{
'rules': [
'originalfilename =~ ".*wbadmin.exe.*" and commandline =~ ".*delete.*"',
'originalfilename =~ ".*wbadmin.exe" and commandline =~ ".*delete.*"',
'originalfilename =~ ".*bcdedit.exe" and commandline =~ ".*recoveryenabled.*no.*"',
'originalfilename =~ ".*bcdedit.exe" and commandline =~ ".*bootstatuspolicy.*ignoreallfailures.*"',
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*shadowcopy.*" and commandline =~ ".*delete.*"',
@@ -64,9 +64,9 @@ rule = [
},
{
'rules': [
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*view.*"',
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*group.*"',
'originalfilename =~ ".*ping.exe"',
'originalfilename == "net.exe" and commandline =~ ".*view.*"',
'originalfilename == "net.exe" and commandline =~ ".*group.*"',
'originalfilename == "ping.exe"',
],
'attck_hit':['T1018'],
@@ -75,7 +75,7 @@ rule = [
},
{
'rules': [
'originalfilename =~ ".*fsutil.exe.*" and commandline =~ ".*deletejournal.*"',
'originalfilename =~ ".*fsutil.exe" and commandline =~ ".*deletejournal.*"',
],
'attck_hit':['T1070.004'],
'score': 10,
@@ -83,11 +83,11 @@ rule = [
},
{
'rules': [
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*user.*"',
'originalfilename =~ ".*whoami.*"',
'originalfilename == ".*net.exe" and commandline =~ ".*user.*"',
'originalfilename =~ ".*whoami.exe"',
'originalfilename =~ ".*query.exe"',
'originalfilename =~ ".*setspn.exe"',
'originalfilename =~ ".*cmdkey.exe.*"'
'originalfilename =~ ".*cmdkey.exe"'
],
'attck_hit':['T1087.001'],
'score': 30,

View File

@@ -1,4 +1,12 @@
rule = [
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe"',
],
'attck_hit':['T1003'],
'score': 100,
'name': 'OS Credential Dumping: LSASS Memory'
},
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe" and grantedaccess & 0x0010 and sourceimage =~ ".*rundll32.exe"',

View File

@@ -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,9 +230,29 @@ def push_process_raw(
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
conn.close()
return result
def query_last_raw_process_log(num):
global g_rawdata_table
sql_session = sessionmaker(bind=g_engine)
# 用g_rawdata_table 不行, utf8编码问题
start_time = int(round(time.time() * 1000))
end_time = start_time - 1000 * 60 * 60 * 24 * 7
raw_log = (
sql_session()
.query(raw_process_log)
# .filter(
# raw_process_log.timestamp >= end_time
# )
.limit(num)
.all()
)
sql_session().close()
return raw_log
def select_process_raw_log_by_time(start: int, end: int):
global g_rawdata_table
sql_session = sessionmaker(bind=g_engine)
@@ -245,7 +267,6 @@ def select_process_raw_log_by_time(start: int, end: int):
)
.all()
)
sql_session().close()
return raw_log
@@ -285,6 +306,7 @@ def update_threat_log(
)
)
result = conn.execute(update)
conn.close()
return result
@@ -298,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
@@ -309,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
@@ -320,6 +344,22 @@ def query_one_threat(threat_id):
return threat
def query_raw_host_log_num(host):
global g_rawdata_table
sql_session = sessionmaker(bind=g_engine)
num = sql_session().query(g_rawdata_table).filter_by(host=host).count()
sql_session().close()
return num
def query_threat_all_num():
global g_threat_table
sql_session = sessionmaker(bind=g_engine)
threat = sql_session().query(g_threat_table).count()
sql_session().close()
return threat
def query_all_threat_log(query_type):
global g_threat_table
sql_session = sessionmaker(bind=g_engine)
@@ -396,5 +436,6 @@ def push_threat_log(
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
conn.close()
# print(raw_json)
return result

66
Server/statistics.py Normal file
View File

@@ -0,0 +1,66 @@
import time
import sql
all_log_num = 0
host_list = {}
last_update_time = 0
def get_host_list():
global host_list
return host_list
def update_host_list(host):
global host_list
host_list[host] = 1
def update_loged_num(host):
global all_log_num
global host_list
global last_update_time
all_log_num += 1
if host not in host_list:
host_list[host] = {
'last_update_time': time.time(),
'log_num': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'all_log_num': 0
}
host_list[host]['all_log_num'] += 1
if time.time() - host_list[host]['last_update_time'] > 60:
if host_list[host]['all_log_num'] == 0:
del host_list[host]
host_list[host]['last_update_time'] = time.time()
host_list[host]['log_num'].append(host_list[host]['all_log_num'])
host_list[host]['all_log_num'] = 0
if len(host_list[host]['log_num']) > 10:
del host_list[host]['log_num'][0]
def get_loged_num():
global all_log_num
if all_log_num > 30000000:
all_log_num = 0
return all_log_num
def get_threat_nums():
# sqlite的count啥的还不如自己查出来自己统计
host_list = get_host_list()
# 懒得做了...
# last_logs = sql.query_last_raw_process_log(10)
# for iter in last_logs:
# print(last_logs)
threat_datas = sql.query_all_threat_log(-1)
return_data = {"all": len(threat_datas), "confirm": 0,
"ingore": 0, "working": 0, "all_log_num": get_loged_num(), "host_list": host_list}
for iter in threat_datas:
if iter[9] == 1:
return_data["confirm"] += 1
elif iter[9] == 2:
return_data["ingore"] += 1
if iter[7] == 0:
return_data["working"] += 1
return return_data

View File

@@ -1 +1 @@
.menu-active{background:#f2c037;color:#fff}::-webkit-scrollbar{height:4px;width:5px}::-webkit-scrollbar-thumb{background:#027be3}::-webkit-scrollbar-thumb,::-webkit-scrollbar-track{border-radius:15px;-webkit-box-shadow:inset 0 0 5px #0003}::-webkit-scrollbar-track{background:#ededed}
.menu-active{background:#f2c037;color:#fff}::-webkit-scrollbar{height:4px;width:5px}::-webkit-scrollbar-thumb{background:#2f2b30}::-webkit-scrollbar-thumb,::-webkit-scrollbar-track{border-radius:15px;-webkit-box-shadow:inset 0 0 5px #0003}::-webkit-scrollbar-track{background:#ededed}

View File

@@ -1 +1 @@
<!DOCTYPE html><html><head><title>Duck Sys Eye</title><meta charset=utf-8><meta name=description content=syseye><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png sizes=128x128 href=icons/favicon-128x128.png><link rel=icon type=image/png sizes=96x96 href=icons/favicon-96x96.png><link rel=icon type=image/png sizes=32x32 href=icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=icons/favicon-16x16.png><link rel=icon type=image/ico href=favicon.ico><script defer src=js/vendor.8b656787.js></script><script defer src=js/app.b7308b45.js></script><link href=css/vendor.5b8581f0.css rel=stylesheet><link href=css/app.31d6cfe0.css rel=stylesheet></head><body><div id=q-app></div></body></html>
<!DOCTYPE html><html><head><title>Duck Sys Eye</title><meta charset=utf-8><meta name=description content=syseye><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png sizes=128x128 href=icons/favicon-128x128.png><link rel=icon type=image/png sizes=96x96 href=icons/favicon-96x96.png><link rel=icon type=image/png sizes=32x32 href=icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=icons/favicon-16x16.png><link rel=icon type=image/ico href=favicon.ico><script defer src=js/vendor.ee689f15.js></script><script defer src=js/app.563fee9e.js></script><link href=css/vendor.5b8581f0.css rel=stylesheet><link href=css/app.31d6cfe0.css rel=stylesheet></head><body><div id=q-app></div></body></html>

View File

@@ -1 +1 @@
"use strict";(globalThis["webpackChunksyseye"]=globalThis["webpackChunksyseye"]||[]).push([[219],{4219:(a,e,t)=>{t.r(e),t.d(e,{default:()=>_});var n=t(3673),s=t(2323);function o(a,e,t,o,i,l){const r=(0,n.up)("q-td"),p=(0,n.up)("q-btn"),d=(0,n.up)("q-tr"),h=(0,n.up)("q-table");return(0,n.wg)(),(0,n.j4)(h,{class:"q-pa-lg",dense:a.$q.screen.lt.md,title:"白名单列表",columns:a.data_columns,rows:a.data_columns_data,loading:a.loading,pagination:a.pagination,"onUpdate:pagination":e[0]||(e[0]=e=>a.pagination=e),onRequest:a.onRequest},{body:(0,n.w5)((e=>[(0,n.Wm)(d,{props:e},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{key:"path",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.path),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"hash",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.hash),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"reason",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.reason),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"timestamp",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(a.time_parase(e.row.timestamp)),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"action",props:e},{default:(0,n.w5)((()=>[(0,n.Wm)(p,{color:"red",label:"移除白名单",onClick:t=>a.delete_white_hash(e.row.hash)},null,8,["onClick"])])),_:2},1032,["props"])])),_:2},1032,["props"])])),_:1},8,["dense","columns","rows","loading","pagination","onRequest"])}var i=t(52),l=t.n(i);const r=(0,n.aZ)({name:"WhiteList",data:function(){return{data_columns:[{name:"path",align:"center",label:"路径",field:"path"},{name:"hash",align:"center",label:"hash",field:"hash"},{name:"reason",align:"center",label:"原因",field:"reason"},{name:"timestamp",align:"center",label:"时间",field:"timestamp"},{name:"action",align:"center",label:"操作",field:"steamid"}],data_columns_data:[],loading:!1,pagination:{sortBy:"desc",descending:!1,page:1,rowsPerPage:10,rowsNumber:10}}},mounted(){this.onRequest({pagination:this.pagination,filter:void 0})},methods:{delete_white_hash(a){l().get("/api/v1/del/white_list?hash="+a).then((a=>{this.onRequest({pagination:this.pagination,filter:void 0})}))},time_parase(a){const e=a=>a<10?"0"+a:a,t=new Date(Number(a));console.log("time",a);const n=t.getFullYear(),s=t.getMonth()+1,o=t.getDate(),i=t.getHours(),l=t.getMinutes(),r=t.getSeconds();return n+"-"+e(s)+"-"+e(o)+" "+e(i)+":"+e(l)+":"+e(r)},onRequest(a){this.data_columns_data=[],this.loading=!0;const{page:e}=a.pagination;l().get("/api/v1/query/white_list_all").then((a=>{const t=a.data.result;console.log(t);for(let e=0;e<t.length;e++){const a=t[e];this.data_columns_data.push(a)}this.pagination.page=e,this.pagination.rowsNumber=this.data_columns_data.length,this.pagination.rowsPerPage=this.data_columns_data.length,this.loading=!1}))}}});var p=t(4260),d=t(1779),h=t(8186),g=t(3884),u=t(8240),m=t(7518),c=t.n(m);const w=(0,p.Z)(r,[["render",o]]),_=w;c()(r,"components",{QTable:d.Z,QTr:h.Z,QTd:g.Z,QBtn:u.Z})}}]);
"use strict";(globalThis["webpackChunksyseye"]=globalThis["webpackChunksyseye"]||[]).push([[219],{4219:(a,e,t)=>{t.r(e),t.d(e,{default:()=>_});var n=t(3673),s=t(2323);function o(a,e,t,o,i,l){const r=(0,n.up)("q-td"),p=(0,n.up)("q-btn"),d=(0,n.up)("q-tr"),h=(0,n.up)("q-table");return(0,n.wg)(),(0,n.j4)(h,{class:"q-pa-lg",dense:a.$q.screen.lt.md,title:"白名单列表",columns:a.data_columns,rows:a.data_columns_data,loading:a.loading,pagination:a.pagination,"onUpdate:pagination":e[0]||(e[0]=e=>a.pagination=e),onRequest:a.onRequest},{body:(0,n.w5)((e=>[(0,n.Wm)(d,{props:e},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{key:"path",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.path),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"hash",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.hash),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"reason",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.row.reason),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"timestamp",props:e},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(a.time_parase(e.row.timestamp)),1)])),_:2},1032,["props"]),(0,n.Wm)(r,{key:"action",props:e},{default:(0,n.w5)((()=>[(0,n.Wm)(p,{color:"red",label:"移除白名单",onClick:t=>a.delete_white_hash(e.row.hash)},null,8,["onClick"])])),_:2},1032,["props"])])),_:2},1032,["props"])])),_:1},8,["dense","columns","rows","loading","pagination","onRequest"])}var i=t(52),l=t.n(i);const r=(0,n.aZ)({name:"WhiteList",data:function(){return{data_columns:[{name:"path",align:"center",label:"路径",field:"path"},{name:"hash",align:"center",label:"hash",field:"hash"},{name:"reason",align:"center",label:"原因",field:"reason"},{name:"timestamp",align:"center",label:"时间",field:"timestamp"},{name:"action",align:"center",label:"操作",field:"steamid"}],data_columns_data:[],loading:!1,pagination:{sortBy:"desc",descending:!1,page:1,rowsPerPage:10,rowsNumber:10}}},mounted(){this.onRequest({pagination:this.pagination,filter:void 0})},methods:{delete_white_hash(a){l().get("/api/v1/del/white_list?hash="+a).then((a=>{this.onRequest({pagination:this.pagination,filter:void 0})}))},time_parase(a){const e=a=>a<10?"0"+a:a,t=new Date(Number(a));console.log("time",a);const n=t.getFullYear(),s=t.getMonth()+1,o=t.getDate(),i=t.getHours(),l=t.getMinutes(),r=t.getSeconds();return n+"-"+e(s)+"-"+e(o)+" "+e(i)+":"+e(l)+":"+e(r)},onRequest(a){this.data_columns_data=[],this.loading=!0;const{page:e}=a.pagination;l().get("/api/v1/query/white_list_all").then((a=>{const t=a.data.result;console.log(t);for(let e=0;e<t.length;e++){const a=t[e];this.data_columns_data.push(a)}this.pagination.page=e,this.pagination.rowsNumber=this.data_columns_data.length,this.pagination.rowsPerPage=this.data_columns_data.length,this.loading=!1}))}}});var p=t(4260),d=t(7898),h=t(8186),g=t(3884),u=t(8240),m=t(7518),c=t.n(m);const w=(0,p.Z)(r,[["render",o]]),_=w;c()(r,"components",{QTable:d.Z,QTr:h.Z,QTd:g.Z,QBtn:u.Z})}}]);

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
"use strict";(globalThis["webpackChunksyseye"]=globalThis["webpackChunksyseye"]||[]).push([[904],{6904:(s,a,e)=>{e.r(a),e.d(a,{default:()=>h});var r=e(3673);const n={class:"row q-gutter-md q-mb-sm q-pa-lg"};function t(s,a,e,t,c,o){return(0,r.wg)(),(0,r.iD)("h4",n,"施工中....")}const c=(0,r.aZ)({name:"Dashboard"});var o=e(4260);const u=(0,o.Z)(c,[["render",t]]),h=u}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,16 +1,16 @@
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__,
template_folder="./templates",
@@ -53,18 +53,7 @@ def plugin_menu():
def threat_statistics():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
# sqlite的count啥的还不如自己查出来自己统计
threat_datas = sql.query_all_threat_log(-1)
return_data = {"all": len(threat_datas), "confirm": 0,
"ingore": 0, "working": 0}
for iter in threat_datas:
if iter[9] == 1:
return_data["confirm"] += 1
elif iter[9] == 2:
return_data["ingore"] += 1
if iter[7] == 0:
return_data["working"] += 1
return {"data": return_data}
return {"data": statistics.get_threat_nums()}
@app.route("/api/v1/query/white_list_all", methods=["GET"])
@@ -196,11 +185,12 @@ def process():
# 转小写
host = request.remote_addr
log.process_log(host, json.loads(body_data.lower()), body_data)
statistics.update_loged_num(host)
return {"status": "success"}
@app.route("/api/v1/log_hunt", methods=["POST"])
@ app.route("/api/v1/log_hunt", methods=["POST"])
def log_rescan():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
@@ -223,4 +213,5 @@ if __name__ == "__main__":
flask_log.setLevel(logging.ERROR)
print("注意,你正在使用测试版,请随时关注github以获取最新版本:")
print("https://github.com/RoomaSec/RmEye")
# statistics.get_threat_nums()
app.run(debug=True, host="0.0.0.0")

View File

@@ -5349,6 +5349,11 @@
}
}
},
"js-base64": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@@ -14,6 +14,7 @@
"axios": "^0.27.2",
"core-js": "^3.6.5",
"echarts": "^5.3.3",
"js-base64": "^3.7.2",
"quasar": "^2.0.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0"

View File

@@ -0,0 +1,117 @@
/* eslint-disable camelcase */
/* eslint-disable eqeqeq */
/* eslint-disable no-useless-escape */
// 1.加密解密方法使用:
// 1.加密
// var str = '124中文内容';
// var base = new Base64();
// var result = base.encode(str);
// //document.write(result);
// //2.解密
// var result2 = base.decode(result);
// document.write(result2);
// //2.加密、解密算法封装:
function Base64 () {
// private property
const _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
// public method for encoding
this.encode = function (input) {
let output = ''
let chr1, chr2, chr3, enc1, enc2, enc3, enc4
let i = 0
input = _utf8_encode(input)
while (i < input.length) {
chr1 = input.charCodeAt(i++)
chr2 = input.charCodeAt(i++)
chr3 = input.charCodeAt(i++)
enc1 = chr1 >> 2
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
enc4 = chr3 & 63
if (isNaN(chr2)) {
enc3 = enc4 = 64
} else if (isNaN(chr3)) {
enc4 = 64
}
output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4)
}
return output
}
// public method for decoding
this.decode = function (input) {
let output = ''
let chr1, chr2, chr3
let enc1, enc2, enc3, enc4
let i = 0
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '')
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++))
enc2 = _keyStr.indexOf(input.charAt(i++))
enc3 = _keyStr.indexOf(input.charAt(i++))
enc4 = _keyStr.indexOf(input.charAt(i++))
chr1 = (enc1 << 2) | (enc2 >> 4)
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
chr3 = ((enc3 & 3) << 6) | enc4
output = output + String.fromCharCode(chr1)
if (enc3 != 64) {
output = output + String.fromCharCode(chr2)
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3)
}
}
output = _utf8_decode(output)
return output
}
// private method for UTF-8 encoding
var _utf8_encode = function (string) {
string = string.replace(/\r\n/g, '\n')
let utftext = ''
for (let n = 0; n < string.length; n++) {
const c = string.charCodeAt(n)
if (c < 128) {
utftext += String.fromCharCode(c)
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192)
utftext += String.fromCharCode((c & 63) | 128)
} else {
utftext += String.fromCharCode((c >> 12) | 224)
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
utftext += String.fromCharCode((c & 63) | 128)
}
}
return utftext
}
// private method for UTF-8 decoding
var _utf8_decode = function (utftext) {
let string = ''
let i = 0
let c = 0, c1 = 0, c2 = 0
while (i < utftext.length) {
c = utftext.charCodeAt(i)
if (c < 128) {
string += String.fromCharCode(c)
i++
} else if ((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i + 1)
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63))
i += 2
} else {
c2 = utftext.charCodeAt(i + 1)
c1 = utftext.charCodeAt(i + 2)
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c1 & 63))
i += 3
}
}
return string
}
}
export default Base64

View File

View File

@@ -1,118 +1,56 @@
<template>
<q-layout view="lHh Lpr lFf" style="background-color: rgb(239, 243, 246)">
<q-layout view="lHh Lpr lFf" :style="`background-color: ${colors.background}`">
<q-header elevated height-hint="98">
<q-toolbar class="text-primary bg-white">
<q-toolbar-title> RmEye内部测试版本v0.0.0.1 </q-toolbar-title>
<q-btn flat round dense icon="more_vert"></q-btn>
</q-toolbar>
<q-toolbar class="text-white" :style="`background-color: ${colors.toolbar}`">
<q-toolbar-title> RmEye测试版v1.0.1.3 </q-toolbar-title>
<q-btn flat dense icon="restore" label="重置颜色" @click="cleanUpCookie">
</q-btn>
<q-popup-proxy>
<q-banner>
<q-color v-model="colors.toolbar" @change="updateCookie(colors.toolbar)" class="my-picker" />
</q-banner>
</q-popup-proxy>
</q-toolbar>
<q-toolbar :style="`font-size: 16px;background-color: ${colors.layout}`">
<q-breadcrumbs active-color="white">
<q-breadcrumbs-el label="仪表盘" icon="dashboard" to="/page/dashboard" />
<q-breadcrumbs-el label="未处理威胁列表" icon="report" to="#" @click="routerToThreatList(0);" />
<q-breadcrumbs-el label="已处理威胁列表" icon="done" to="#" @click="routerToThreatList(1);" />
<q-breadcrumbs-el label="已忽略威胁列表" icon="texture" to="#" @click="routerToThreatList(2);" />
<q-breadcrumbs-el label="白名单列表" icon="list" to="#" @click="routerToWhiteList();" />
</q-breadcrumbs>
<q-popup-proxy>
<q-banner>
<q-color v-model="colors.layout" @change="updateCookie(colors.layout)" class="my-picker" />
</q-banner>
</q-popup-proxy>
</q-toolbar>
</q-header>
<q-drawer
show-if-above
:mini="miniState"
@mouseover="miniState = false"
@mouseout="miniState = true"
:width="200"
:breakpoint="500"
bordered
class="bg-white text-primary"
>
<q-scroll-area class="fit">
<q-list padding>
<q-item
:active="selectLabel == 'dashboard'"
clickable
v-ripple
active-class="menu-active"
@click="selectLabel = 'dashboard'"
to="/page/dashboard"
>
<q-item-section avatar>
<q-icon name="dashboard" />
</q-item-section>
<q-item-section> 仪表盘 </q-item-section>
</q-item>
<q-item
:active="selectLabel == 'non_hanlde_report'"
clickable
v-ripple
active-class="menu-active"
@click="
selectLabel = 'non_hanlde_report';
routerToThreatList(0);
"
>
<q-item-section avatar>
<q-icon name="report" />
</q-item-section>
<q-item-section> 未处理威胁列表 </q-item-section>
</q-item>
<q-item
:active="selectLabel == 'handle_report'"
clickable
v-ripple
active-class="menu-active"
@click="
selectLabel = 'handle_report';
routerToThreatList(1);
"
>
<q-item-section avatar>
<q-icon name="done" />
</q-item-section>
<q-item-section> 已处理威胁列表 </q-item-section>
</q-item>
<q-item
:active="selectLabel == 'ingore_report'"
clickable
v-ripple
active-class="menu-active"
@click="
selectLabel = 'ingore_report';
routerToThreatList(2);
"
>
<q-item-section avatar>
<q-icon name="texture" />
</q-item-section>
<q-item-section> 已忽略威胁列表 </q-item-section>
</q-item>
<q-item
:active="selectLabel == 'white_list'"
clickable
v-ripple
active-class="menu-active"
@click="
selectLabel = 'white_list';
routerToWhiteList();
"
>
<q-item-section avatar>
<q-icon name="list" />
</q-item-section>
<q-item-section> 白名单列表 </q-item-section>
</q-item>
</q-list>
</q-scroll-area>
</q-drawer>
<template v-if="isInPlugin == false">
<q-page-container>
<router-view />
</q-page-container>
<q-page-container>
<router-view />
</q-page-container>
</template>
<template v-if="isInPlugin">
<div class="q-gutter-md q-mb-sm q-pa-lg">
<HtmlPanel v-model:url="PluginUrl" />
</div>
<div class="q-gutter-md q-mb-sm q-pa-lg">
<HtmlPanel v-model:url="PluginUrl" />
</div>
</template>
</q-layout>
</q-layout>
</template>
<script>
import { defineComponent } from 'vue'
import Base64 from '../assets/b64.js'
import {
defineComponent
} from 'vue'
import HtmlPanel from '../components/Html.vue' // 根据实际路径导入
import axios from 'axios'
import {
Cookies
} from 'quasar'
export default defineComponent({
components: {
HtmlPanel
@@ -123,15 +61,31 @@ export default defineComponent({
},
data: function () {
return {
selectLabel: 'non_hanlde_report',
selectLabel: 'dashboard',
drawer: false,
miniState: true,
plugin: [],
isInPlugin: false,
PluginUrl: ''
PluginUrl: '',
colors: {
layout: 'rgb(47,43,48)',
toolbar: 'rgb(210,61,42)',
background: 'rgb(239, 243, 246)'
}
}
},
methods: {
updateCookie (selectItem) {
const b64Obj = new Base64()
Cookies.set('custom_banner', b64Obj.encode(JSON.stringify(this.colors)))
},
cleanUpCookie () {
Cookies.remove('custom_threat_item')
Cookies.remove('custom_banner')
// refesh
window.location.reload()
},
routerToWhiteList () {
this.isInPlugin = false
this.$router.push({
@@ -164,6 +118,11 @@ export default defineComponent({
},
mounted () {
this.getPluginsMenu()
const coockieCustomBanner = Cookies.get('custom_banner')
if (coockieCustomBanner) {
const b64Obj = new Base64()
this.colors = JSON.parse(b64Obj.decode(coockieCustomBanner))
}
}
})
</script>
@@ -185,7 +144,7 @@ export default defineComponent({
/*滚动条里面小方块*/
border-radius: 15px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #027be3;
background: rgb(47,43,48);
}
::-webkit-scrollbar-track {

View File

@@ -1,10 +1,220 @@
<template>
<h4 class="row q-gutter-md q-mb-sm q-pa-lg">施工中....</h4>
</template>
<script>
import { defineComponent } from 'vue'
<div class="q-gutter-md q-mb-sm q-pa-lg">
<div>
<q-card class="bg-transparent no-shadow no-border">
<q-card-section class="q-pa-none">
<div class="row q-col-gutter-sm">
<div v-for="(item, index) in Threatitems" :key="index" class="col-md-3 col-sm-12 col-xs-12">
<q-item :style="`background-color: ${item.color1}`" class="q-pa-none">
<q-item-section side :style="`background-color: ${item.color2}`" class="q-pa-lg q-mr-none text-white">
<q-icon :name="item.icon" color="white" size="24px"></q-icon>
</q-item-section>
<q-item-section class="q-pa-md q-ml-none text-white">
<q-item-label class="text-white text-h6 text-weight-bolder">{{
item.value
}}</q-item-label>
<q-item-label>{{ item.title }}</q-item-label>
<q-popup-proxy>
<q-banner>
<q-color v-model="item.color1" @change="updateCookie(Threatitems[index].color1)" class="my-picker" />
</q-banner>
</q-popup-proxy>
</q-item-section>
<q-popup-proxy>
<q-banner>
<q-color v-model="item.color2" @change="updateCookie(Threatitems[index].color2)" class="my-picker" />
</q-banner>
</q-popup-proxy>
</q-item>
</div>
</div>
</q-card-section>
</q-card>
</div>
<q-card class="no-shadow" style="background: rbg(255,255,255)">
<q-card-section>
<div class="text-h6">
主机数量: {{threatStatistics.host_num}}/50 <q-icon name="info" class="text-brown cursor-pointer">
<q-popup-proxy transition-show="flip-up" transition-hide="flip-down">
<q-banner class="bg-brown text-white">
<template v-slot:avatar>
<q-icon name="lightbulb" />
</template>
由于python+sqlite数据库作为后端,理论上最高支持的主机数量为50.
</q-banner>
</q-popup-proxy>
</q-icon>
</div>
<div class="text-subtitle2">最近日志数量: {{threatStatistics.all_log_num}}</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div ref="main_draw" style="width: 100%; height: 600px; ">
1
</div>
</q-card-section>
</q-card>
</div>
</template>
<script>
import Base64 from '../assets/b64.js'
import {
defineComponent
} from 'vue'
import axios from 'axios'
import {
Cookies
} from 'quasar'
import * as echarts from 'echarts'
export default defineComponent({
name: 'Dashboard'
name: 'Dashboard',
data () {
return {
Threatitems: [{
title: '发现的威胁',
icon: 'remove_red_eye',
value: '200',
color1: '#EE9B00',
color2: '#EE9B00'
},
{
title: '确认的威胁',
icon: 'flash_on',
value: '500',
color1: '#CA6702',
color2: '#CA6702'
},
{
title: '忽略的威胁',
icon: 'add_moderator',
value: '50',
color1: '#BB3E03',
color2: '#BB3E03'
},
{
title: '进行中的威胁',
icon: 'stream',
value: '1020',
color1: '#AE2012',
color2: '#AE2012'
}
],
threatStatistics: {
all: 1,
confirm: 0,
ingore: 1,
working: 0,
host_list: {},
host_num: 10,
all_log_num: 647
}
}
},
methods: {
updateCookie (selectItem) {
const b64Obj = new Base64()
Cookies.set('custom_threat_item', b64Obj.encode(JSON.stringify(this.Threatitems)))
},
get_threatStatistics () {
axios
.get('/api/v1/get/threat_statistics', {
'Content-Type': 'application/json'
})
.then((response) => {
const data = response.data
if (data.data) {
this.threatStatistics = data.data
this.threatStatistics.host_num = Object.keys(this.threatStatistics.host_list).length
// Threatitems
this.Threatitems[0].value = this.threatStatistics.all
this.Threatitems[1].value = this.threatStatistics.confirm
this.Threatitems[2].value = this.threatStatistics.ingore
this.Threatitems[3].value = this.threatStatistics.working
console.log(this.threatStatistics)
this.draw()
}
})
},
draw () {
const hostList = []
const hostLoggedNumList = []
for (const key in this.threatStatistics.host_list) {
hostList.push(key)
hostLoggedNumList.push({
itemStyle: {
color: '#005F73'
},
name: key,
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: this.threatStatistics.host_list[key].log_num
})
}
const dom = this.$refs.main_draw
const myChart = echarts.init(dom)
const option = {
title: {
text: '最近十分钟日志量'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: hostList
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
data: ['10min', '9min', '8min', '7min', '6min', '5min', '4min', '3min', '2min', '1min']
}],
yAxis: [{
type: 'value'
}],
series: hostLoggedNumList
}
myChart.setOption(option)
setTimeout(() => {
myChart.resize()
}, 1000)
}
},
mounted () {
this.get_threatStatistics()
setInterval(() => {
this.get_threatStatistics()
}, 10000)
const cookieCustomThreatItem = Cookies.get('custom_threat_item')
if (cookieCustomThreatItem) {
const b64Obj = new Base64()
this.Threatitems = JSON.parse(b64Obj.decode(cookieCustomThreatItem))
}
}
})
</script>

View File

@@ -1,26 +1,5 @@
<template>
<div>
<div class="q-gutter-md q-mb-sm q-pa-lg">
<q-card class="bg-transparent no-shadow no-border">
<q-card-section class="q-pa-none">
<div class="row q-col-gutter-sm">
<div v-for="(item, index) in Threatitems" :key="index" class="col-md-3 col-sm-12 col-xs-12">
<q-item :style="`background-color: ${item.color1}`" class="q-pa-none">
<q-item-section side :style="`background-color: ${item.color2}`" class="q-pa-lg q-mr-none text-white">
<q-icon :name="item.icon" color="white" size="24px"></q-icon>
</q-item-section>
<q-item-section class="q-pa-md q-ml-none text-white">
<q-item-label class="text-white text-h6 text-weight-bolder">{{
item.value
}}</q-item-label>
<q-item-label>{{ item.title }}</q-item-label>
</q-item-section>
</q-item>
</div>
</div>
</q-card-section>
</q-card>
</div>
<div class="row">
<div class="col"></div>
<div class="col">
@@ -133,7 +112,7 @@
</div>
<q-drawer show-if-above v-if="processChainShowDetails" v-model="processChainShowDetails" side="right" bordered width="350" class="text-dark">
<q-list style="width: 100%;word-break: break-all;">
<q-item>
<q-item>
<q-item-section>活跃状态: {{processChainDetails.active ? "运行中" : "已结束"}}</q-item-section>
</q-item>
<q-separator />
@@ -161,13 +140,13 @@
<q-item-section>进程hash: {{processChainDetails.md5}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item>
<q-item-section>是否在白名单中: {{processChainDetails.isWhite ? "是" : "否"}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程命中的规则:
<template v-for="(index, operation) in processChainDetails.hitRules" :key="index">
<template v-for="(index, operation) in processChainDetails.hitRules" :key="index">
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
@@ -181,7 +160,7 @@
</q-item>
<q-item>
<q-item-section>attck矩阵:
<template v-for="(index, operation) in processChainDetails.hitAttck" :key="index">
<template v-for="(index, operation) in processChainDetails.hitAttck" :key="index">
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
@@ -251,41 +230,6 @@ export default defineComponent({
width: '9px',
opacity: 0.2
},
threatStatistics: {
all: 1,
confirm: 0,
ingore: 1,
working: 0
},
Threatitems: [{
title: '发现的威胁',
icon: 'remove_red_eye',
value: '200',
color1: '#5064b5',
color2: '#3e51b5'
},
{
title: '确认的威胁',
icon: 'flash_on',
value: '500',
color1: '#f37169',
color2: '#f34636'
},
{
title: '忽略的威胁',
icon: 'texture',
value: '50',
color1: '#ea6a7f',
color2: '#ea4b64'
},
{
title: '进行中的威胁',
icon: 'bar_chart',
value: '1020',
color1: '#a270b1',
color2: '#9f52b1'
}
],
dialog: false,
maximizedToggle: true,
server_threat: {},
@@ -453,23 +397,7 @@ export default defineComponent({
}
})
},
get_threatStatistics () {
axios
.get('/api/v1/get/threat_statistics', {
'Content-Type': 'application/json'
})
.then((response) => {
const data = response.data
if (data.data) {
this.threatStatistics = data.data
// Threatitems
this.Threatitems[0].value = this.threatStatistics.all
this.Threatitems[1].value = this.threatStatistics.confirm
this.Threatitems[2].value = this.threatStatistics.ingore
this.Threatitems[3].value = this.threatStatistics.working
}
})
},
get_clientids () {
const queryType = this.$route.params.queryIndex
const queryIndex = (queryType === null || queryType === undefined) ? 0 : queryType
@@ -484,7 +412,6 @@ export default defineComponent({
data: []
}
this.server_threat.data = data.data
this.get_threatStatistics()
}
})
}

View File

@@ -4,7 +4,7 @@ const routes = [
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/Index.vue') }
{ path: '', component: () => import('pages/Dashboard.vue') }
]
},
{

View File

@@ -1,11 +1,9 @@
![image](Image/logo.png)
![image](Image/wx.png)
# RmEye
RmEye是一个window上的基于att&ck现代EDR设计思想的威胁响应工具.
不同于EDR,它轻量、高效.自身定位是轻量级威胁检出工具.
而不是繁重的、需要付费的、效果不明的所谓的EDR
RmEye基于att&ck模型,如果您对att&ck模型不熟悉,请先阅读相关文章后再使用:
https://key08.com/index.php/2022/08/09/1505.html
### 功能特点
1. 基于att&ck设计.所有设计只是为了符合att&ck的攻击路径、攻击链(虽然规则里面没有标注T因为懒惰)
@@ -25,6 +23,15 @@ https://key08.com/index.php/2022/08/09/1505.html
请牢记,RmEye自身定位是轻量级威胁检出工具
### 最新新闻
2022/10/11:
重新设计了一下界面...
2022/9/29:
国庆节更新,增加ip与hash的ioc插件,目前Rmeye有能力对ip和hash进行标注,使用时务必换成自己的apikey,其他请看下面的ioc部分
2022/9/22:
增加仪表盘,可视化展示检测结果
2022/9/21:
修复了秋季更新的几个bug,增加了`networkconnect``FileCreateTimeChange`的ds,增加了`brc4`的检测
@@ -47,8 +54,19 @@ https://github.com/RoomaSec/RmEye/blob/main/doc_day0_rule.md
增加uac提权检测插件`uac_bypass_detect`,但是受限于sysmon,没有办法获取RPC信息,因此只能检测一部分的UAC提权行为.并且有误报,请酌情考虑
### 检出截图
新dashboard(2022/10/11更新):
![image](Image/dashboard_new.png)
新界面(2022/10/11更新):
![image](Image/18.png)
IOC(2022/10/1更新):
![image](Image/16.png)
![image](Image/17.png)
威胁列表(2022/9/20更新):
![image](Image/1.png)
仪表盘(2022/9/22更新):
![image](Image/dashboard.png)
进程链行为回溯
![image](Image/8.png)
powershell恶意执行:
@@ -135,6 +153,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的规则支持是以插件的形式支持
@@ -173,12 +194,6 @@ https://github.com/VirusTotal/yara
https://github.com/SwiftOnSecurity/sysmon-config
请遵守相关库的开源协议.相关法律风险本项目不负任何责任
### 交流
开源的目的不是为了免费填鸭式教学,或者被免费拿去发公众号引流、去拿去集成产品方案去赚钱,而是要一起完善这个工具,从而实现共赢.
扫一扫加入这个工具的交流群,这样就能获取实时动态.参与开发、参与交流规则编写等等.欢迎加入
最近进群的人有点多,所以不活跃的哥们暂时清理掉,但是微信太不好使了.要是t错了或者还想在群待着不发言的重新加群吧
![image](Image/group.png)
### 特别感谢
@Pwn0x01 yara插件
@zeroSteiner 规则引擎插件

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Flask==2.2.2
requests==2.28.1
rule_engine==3.5.0
SQLAlchemy==1.4.42

View File

@@ -282,7 +282,7 @@
<Image name="Usermode" condition="begin with">C:\Users</Image> <!--Tools downloaded by users can use other processes for networking, but this is a very valuable indicator.-->
<Image name="Caution" condition="begin with">C:\Recycle</Image> <!--Nothing should operate from the RecycleBin locations.-->
<Image condition="begin with">C:\ProgramData</Image> <!--Normally, network communications should be sourced from "Program Files" not from ProgramData, something to look at-->
<Image condition="begin with">C:\Windows\Temp</Image> <!--Suspicious anything would communicate from the system-level temp directory-->
<Image condition="begin with">C:\Windows\</Image> <!--Suspicious anything would communicate from the system-level temp directory-->
<Image name="Caution" condition="begin with">\</Image> <!--Devices and VSC shouldn't be executing changes | Credit: @SBousseaden @ionstorm @neu5ron @PerchedSystems [ https://twitter.com/SwiftOnSecurity/status/1133167323991486464 ] -->
<Image name="Caution" condition="begin with">C:\perflogs</Image> <!-- Credit @blu3_team [ https://blu3-team.blogspot.com/2019/05/netconn-from-suspicious-directories.html ] -->
<Image name="Caution" condition="begin with">C:\intel</Image> <!-- Credit @blu3_team [ https://blu3-team.blogspot.com/2019/05/netconn-from-suspicious-directories.html ] -->
@@ -376,8 +376,15 @@
<RuleGroup name="" groupRelation="or">
<NetworkConnect onmatch="exclude">
<Image condition="end with">clash-win64.exe</Image>
<Image condition="end with">dasHost.exe</Image>
<Image condition="end with">DingTalk.exe</Image>
<Image condition="end with">vmnat.exe</Image>
<Image condition="end with">SysEye.exe</Image>
<!--SECTION: Microsoft-->
<Image condition="begin with">C:\ProgramData\Microsoft\Windows Defender\Platform\</Image>
<Image condition="is">C:\Windows\system32\svchost.exe</Image> <!--Microsoft: svchost-->
<Image condition="end with">AppData\Local\Microsoft\Teams\current\Teams.exe</Image> <!--Microsoft: Teams-->
<DestinationHostname condition="end with">.microsoft.com</DestinationHostname> <!--Microsoft:Update delivery-->
<DestinationHostname condition="end with">microsoft.com.akadns.net</DestinationHostname> <!--Microsoft:Update delivery-->
@@ -423,7 +430,34 @@
<!--DATA: UtcTime, ProcessGuid, ProcessId, Image, ImageLoaded, Hashes, Signed, Signature, SignatureStatus-->
<RuleGroup name="" groupRelation="or">
<ImageLoad onmatch="include">
<!--NOTE: Using "include" with no rules means nothing in this section will be logged-->
<ImageLoaded condition="contains">samlib.dll</ImageLoaded>
<ImageLoaded condition="contains">advapi32.dll</ImageLoaded>
<ImageLoaded condition="contains">crypt32.dll</ImageLoaded>
<ImageLoaded condition="contains">cryptdll.dll</ImageLoaded>
<ImageLoaded condition="contains">gdi32.dll</ImageLoaded>
<ImageLoaded condition="contains">imm32.dll</ImageLoaded>
<ImageLoaded condition="contains">msasn1.dll</ImageLoaded>
<ImageLoaded condition="contains">msvcrt.dll</ImageLoaded>
<ImageLoaded condition="contains">rpcrt4.dll</ImageLoaded>
<ImageLoaded condition="contains">rsaenh.dll</ImageLoaded>
<ImageLoaded condition="contains">samlib.dll</ImageLoaded>
<ImageLoaded condition="contains">sechost.dll</ImageLoaded>
<ImageLoaded condition="contains">secur32.dll</ImageLoaded>
<ImageLoaded condition="contains">shell32.dll</ImageLoaded>
<ImageLoaded condition="contains">shlwapi.dll</ImageLoaded>
<ImageLoaded condition="contains">sspicli.dll</ImageLoaded>
<ImageLoaded condition="contains">user32.dll</ImageLoaded>
<ImageLoaded condition="contains">vaultcli.dll</ImageLoaded>
<ImageLoaded condition="contains">dbghelp.dll</ImageLoaded>
<ImageLoaded condition="contains">winhttp.dll</ImageLoaded>
<ImageLoaded condition="contains">credui.dll</ImageLoaded>
<ImageLoaded condition="contains">dnsapi.dll</ImageLoaded>
<ImageLoaded condition="contains">rtutils.dll</ImageLoaded>
<ImageLoaded condition="contains">urlmon.dll</ImageLoaded>
<ImageLoaded condition="contains">sensapi.dll</ImageLoaded>
<ImageLoaded condition="contains">rasapi32.dll</ImageLoaded>
<ImageLoaded condition="contains">napinsp.dll</ImageLoaded>
</ImageLoad>
</RuleGroup>
@@ -585,7 +619,6 @@
<TargetFilename name="T1176" condition="end with">.crx</TargetFilename> <!--Chrome extension-->
<TargetFilename condition="end with">.dmp</TargetFilename> <!--Process dumps [ (fr) http://blog.gentilkiwi.com/securite/mimikatz/minidump ] -->
<TargetFilename condition="end with">.docm</TargetFilename> <!--Microsoft:Office:Word: Macro-->
<TargetFilename condition="end with">.otm</TargetFilename> <!--Microsoft:Office:VBS: Macro-->
<TargetFilename name="DLL" condition="end with">.dll</TargetFilename> <!--Microsoft:Office:Word: Macro-->
<TargetFilename name="EXE" condition="end with">.exe</TargetFilename> <!--Executable-->
<TargetFilename name="ProcessHostingdotNETCode" condition="end with">.exe.log</TargetFilename> <!-- [ https://github.com/bitsadmin/nopowershell ] | Credit: @SBousseaden [ https://twitter.com/SBousseaden/status/1137493597769687040 ] -->