61 Commits

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
huoji
a1c158f8cd 增加BRC4的检测
增加BRC4的检测
2022-09-21 15:28:07 +08:00
huoji
ee5ae888ce 更新readme
更新readme
2022-09-20 18:40:06 +08:00
huoji
1ef79280fc Merge branch 'main' of https://github.com/RoomaSec/RmEye 2022-09-20 18:31:32 +08:00
huoji
05aea0a27b 秋季更新
秋季更新
2022-09-20 18:31:15 +08:00
Huoji's
1ec37eae02 增加微信群 2022-09-13 21:42:51 +08:00
Huoji's
83d1d97e57 Merge pull request #2 from Fplyth0ner-Combie/main
Add: New detection rule about microsoft outlook
2022-09-11 15:23:01 +08:00
Fplyth0ner
343e50a39d Update action.py
Add a new detect rule is named “已知Outlook模板宏持久化行为”.
2022-09-11 00:20:55 +08:00
Fplyth0ner
2ac1b425c7 Update sysmon.xml
Add Microsoft Outlook Tamplate Macro file creation event.
2022-09-11 00:07:39 +08:00
huoji
5fcfd6ec02 删除白名单的东西应该要刷新
删除白名单的东西应该要刷新
2022-09-09 11:44:19 +08:00
huoji
57994f9100 update 2022-09-08 16:29:04 +08:00
huoji
61835326ef update 2022-09-08 16:27:37 +08:00
Huoji's
363a2baf17 Merge pull request #1 from Fplyth0ner-Combie/main
Docs: 新增服务端规则指南
2022-09-08 16:26:09 +08:00
Fplyth0ner
80d3964320 Docs: 新增服务端规则指南 2022-09-08 15:42:44 +08:00
huoji
451bca454c Merge branch 'main' of https://github.com/RoomaSec/RmEye 2022-09-06 11:31:15 +08:00
huoji
6826a9e5be Update mimikatz_detect.py 2022-09-06 11:31:12 +08:00
Huoji's
a40885683c Update README.md 2022-09-05 22:31:13 +08:00
huoji
b33043f8b6 优化一下
优化一下
2022-09-05 17:50:44 +08:00
huoji
c2f44adc2e 修复点bug
修复点bug
2022-09-05 17:36:46 +08:00
huoji
30880f8aa9 Update log.py 2022-09-05 17:35:17 +08:00
huoji
9124f617f5 增加mimikatz检测
增加mimikatz检测
2022-09-05 17:33:00 +08:00
huoji
cde86d8b6c Update group2.png 2022-09-05 17:01:09 +08:00
huoji
642ca43cdc Update README.md 2022-09-05 16:47:31 +08:00
huoji
d503827ad0 增加规则编写教程 2022-09-05 16:46:47 +08:00
huoji
fe69282d89 白名单现在看父进程,如果父进程是白名单的子进程产生的行为都加白(不确定是否可靠,有待观察).
白名单现在看父进程,如果父进程是白名单的子进程产生的行为都加白(不确定是否可靠,有待观察).
2022-09-02 15:23:34 +08:00
huoji
e3ae734150 增加白名单、进程链增加详细信息
增加白名单、进程链增加详细信息
2022-08-31 17:52:26 +08:00
huoji
5c15aa975d Update .gitignore 2022-08-30 15:08:25 +08:00
huoji
628c87facc 1 2022-08-30 15:08:15 +08:00
huoji
816c32c899 Update group.png 2022-08-30 15:06:13 +08:00
huoji
fb1263043a Update webserver.py 2022-08-29 20:01:09 +08:00
huoji
fd44c23181 Update webserver.py 2022-08-29 20:00:30 +08:00
huoji
ae90a158bd Update prcoess_chain_detect.py 2022-08-29 20:00:02 +08:00
huoji
5b4f9c32c4 Merge branch 'main' of https://github.com/RoomaSec/RmEye 2022-08-29 18:46:59 +08:00
huoji
d3907bb427 增加uac提权检测 2022-08-29 18:46:56 +08:00
www
a60414b15c 增加日志回扫功能 2022-08-24 18:06:27 +08:00
huoji
fd360c9995 Update README.md 2022-08-23 11:44:44 +08:00
71 changed files with 3233 additions and 1094 deletions

1
.gitignore vendored
View File

@@ -153,3 +153,4 @@ cython_debug/
*.db
*.zip
*.db-journal

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 224 KiB

BIN
Image/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
Image/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
Image/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
Image/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
Image/14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
Image/15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

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/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
Image/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
Image/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 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: 593 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,4 +1,4 @@
# 检出阈值,越高越难检出但是也会越准确
MAX_THREAT_SCORE = 170
MAX_THREAT_SCORE = 45
# 授权访问主站的IP列表.如果不在后台里面则不能访问后台
ALLOW_ACCESS_IP = ['127.0.0.1']
ALLOW_ACCESS_IP = ['127.0.0.1', '192.168.111.189', '192.168.111.187']

56
Server/hash_white_list.py Normal file
View File

@@ -0,0 +1,56 @@
import process
import sql
g_white_list = []
g_white_dll_load_list = [
'c:\\windows\\system32\\advapi32.dll',
'c:\\windows\\system32\\crypt32.dll',
'c:\\windows\\system32\\cryptdll.dll',
'c:\\windows\\system32\\gdi32.dll',
'c:\\windows\\system32\\imm32.dll',
'c:\\windows\\system32\\kernel32.dll',
'c:\\windows\\system32\\kernelbase.dll',
'c:\\windows\\system32\\msasn1.dll',
'c:\\windows\\system32\\msvcrt.dll',
'c:\\windows\\system32\\ntdll.dll',
'c:\\windows\\system32\\rpcrt4.dll',
'c:\\windows\\system32\\rsaenh.dll',
'c:\\windows\\system32\\samlib.dll',
'c:\\windows\\system32\\sechost.dll',
'c:\\windows\\system32\\secur32.dll',
'c:\\windows\\system32\\shell32.dll',
'c:\\windows\\system32\\shlwapi.dll',
'c:\\windows\\system32\\sspicli.dll',
'c:\\windows\\system32\\user32.dll',
'c:\\windows\\system32\\vaultcli.dll',
]
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:
return False
g_white_list.append(hash)
sql.push_white_list(path, hash, reason)
def synchronization_white_list():
sql_data = sql.query_all_white_list()
for data in sql_data:
g_white_list.append(data[1])
print("sync white list success, size: {}".format(len(sql_data)))

View File

@@ -7,112 +7,73 @@ import sql
import global_vars
import config
import plugin
import hash_white_list
LOG_TYPE_PROCESS_CREATE = 1
LOG_TYPE_PROCESS_ACTION = 2
def process_log(host, json_log, raw_log):
log = json_log["data"]
def update_att_ck(process: process.Process, score, hit_name, attck_t_list):
if hash_white_list.check_in_while_list(process):
score = 0
for t in attck_t_list:
process.set_attck(score, t, hit_name)
# 更新命中的规则
return global_vars.THREAT_TYPE_PROCESS
def update_threat(process: process.Process, score, rule_hit_name):
had_threat = global_vars.THREAT_TYPE_NONE
current_process: process.Process = None
rule_hit_name = ""
score = 0
chain_hash = ""
params = ""
user = ""
if hash_white_list.check_in_while_list(process):
score = 0
if score > 0:
# 更新命中的规则
process.set_score(score, rule_hit_name)
had_threat = global_vars.THREAT_TYPE_PROCESS
return had_threat
if json_log["action"] == "processcreate":
pid = log["processid"]
ppid = log["parentprocessid"]
path = log["image"]
params = log["commandline"]
user = log["user"]
hash = log["hashes"].split(",")[0].split("=")[1]
parent_pid = log["parentprocessid"]
parent_ppid = parent_pid
parent_path = log["parentimage"]
parent_params = log["parentcommandline"]
parent_user = log["parentuser"]
create_time = int(round(time.time() * 1000))
def match_threat(process: process.Process, log, log_type):
had_threat = global_vars.THREAT_TYPE_NONE
success_match = False
hit_name = ''
hit_score = 0
is_ioa = False
if log_type == LOG_TYPE_PROCESS_CREATE:
success_match, is_ioa, attck_t_list, hit_score, rule_hit_name = rule.calc_score_in_create_process(
log)
elif log_type == LOG_TYPE_PROCESS_ACTION:
success_match, is_ioa, attck_t_list, hit_score, rule_hit_name = rule.calc_score_in_action(
log)
if success_match == False:
return had_threat, is_ioa, hit_name, hit_score
# 匹配到了首先更新att&ck的t
had_threat = update_att_ck(
process, hit_score, rule_hit_name, attck_t_list)
hit_name = rule_hit_name
if is_ioa:
had_threat = update_threat(
process, hit_score, rule_hit_name)
else:
is_match_software, software_name, software_score = rule.match_att_ck_software(
process.chain.attck_hit_list)
if is_match_software:
# 匹配到software了,设置为ioa
had_threat = update_threat(
process, software_score, software_name)
hit_name = software_name
hit_score = software_score
#print('match_threat', process.path, is_ioa, hit_name, hit_score)
# if had_threat != global_vars.THREAT_TYPE_NONE:
# print('path: {} hit_name: {} socre: {}'.format(
# process.path, hit_name, hit_score))
return had_threat, is_ioa, hit_name, hit_score
if path in process.skip_process_path or path in process.skip_process_path:
return
parent_process: process.Process = process.get_process_by_pid(ppid)
score, rule_hit_name = rule.calc_score_in_create_process(log)
if hash in process.skip_md5:
return
if parent_process is None or parent_path in process.root_process_path:
# build a process
parent_process = process.Process(
parent_pid,
parent_ppid,
parent_path,
parent_params,
create_time - 1,
"None",
parent_user,
host,
)
child = process.Process(
pid, ppid, path, params, create_time, hash, parent_user, host
)
chain = process.create_chain(parent_process)
chain.add_process(child, parent_pid)
current_process = child
if score > 0:
child.set_score(score, rule_hit_name)
had_threat = global_vars.THREAT_TYPE_PROCESS
else:
child = process.Process(
pid, ppid, path, params, create_time, hash, user, host
)
parent_process.chain.add_process(child, ppid)
current_process = child
if score > 0:
child.set_score(score, rule_hit_name)
had_threat = global_vars.THREAT_TYPE_PROCESS
had_threat_plugin = plugin.dispath_rule_new_process_create(
host, current_process, raw_log, json_log
)
if had_threat == global_vars.THREAT_TYPE_NONE:
had_threat = had_threat_plugin
elif json_log["action"] == "processterminal":
pid = log["processid"]
current_process = process.get_process_by_pid(pid)
if current_process is not None:
plugin.dispath_process_terminal(host, current_process, raw_log, json_log)
current_process.active = False
current_process.chain.terminate_count += 1
if current_process.chain.terminate_count >= (
current_process.chain.active_count - 1
):
current_process.chain.active = False
if current_process.chain.risk_score >= config.MAX_THREAT_SCORE:
sql.update_threat_log(
host,
current_process.chain.risk_score,
json.dumps(current_process.chain.operationlist),
current_process.chain.hash,
current_process.chain.get_json(),
global_vars.THREAT_TYPE_PROCESS,
True,
)
process.g_ProcessChainList.remove(current_process.chain)
elif "processid" in log:
current_process = process.get_process_by_pid(log["processid"])
if current_process is not None:
log["action"] = json_log["action"]
score, rule_hit_name = rule.calc_score_in_action(log)
if score > 0:
current_process.set_score(score, rule_hit_name)
had_threat = global_vars.THREAT_TYPE_PROCESS
had_threat_plugin = plugin.dispath_rule_new_process_action(
host, current_process, raw_log, json_log
)
if had_threat == global_vars.THREAT_TYPE_NONE:
had_threat = had_threat_plugin
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()
@@ -137,6 +98,7 @@ def process_log(host, json_log, raw_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,
@@ -147,11 +109,123 @@ def process_log(host, json_log, raw_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
current_process: process.Process = None
rule_hit_name = ""
score = 0
chain_hash = ""
params = ""
user = ""
is_ioa = False
if json_log["action"] == "processcreate":
pid = log["processid"]
ppid = log["parentprocessid"]
path = log["image"]
params = log["commandline"]
user = log["user"]
hash = log["hashes"].split(",")[0].split("=")[1]
parent_pid = log["parentprocessid"]
parent_ppid = parent_pid
parent_path = log["parentimage"]
parent_params = log["parentcommandline"]
parent_user = log["parentuser"]
create_time = int(round(time.time() * 1000))
if path in process.skip_process_path or path in process.skip_process_path:
return
parent_process: process.Process = process.get_process_by_pid(ppid)
if hash in process.skip_md5:
return
if parent_process is None or parent_path in process.root_process_path:
# build a process
parent_process = process.Process(
parent_pid,
parent_ppid,
parent_path,
parent_params,
create_time - 1,
"None",
parent_user,
host,
)
is_white_list = hash in hash_white_list.g_white_list
child = process.Process(
pid, ppid, path, params, create_time, hash, parent_user, host, is_white_list
)
parent_process.parent_process = parent_process
child.parent_process = parent_process
chain = process.create_chain(parent_process)
chain.add_process(child, parent_pid)
current_process = child
had_threat, is_ioa, rule_hit_name, score = match_threat(
current_process, log, LOG_TYPE_PROCESS_CREATE)
else:
is_white_list = hash in hash_white_list.g_white_list
child = process.Process(
pid, ppid, path, params, create_time, hash, user, host, is_white_list
)
child.parent_process = parent_process
parent_process.chain.add_process(child, ppid)
current_process = child
had_threat, is_ioa, rule_hit_name, score = match_threat(
current_process, log, LOG_TYPE_PROCESS_CREATE)
had_threat_plugin = plugin.dispath_rule_new_process_create(
host, current_process, raw_log, json_log
)
if had_threat == global_vars.THREAT_TYPE_NONE:
had_threat = had_threat_plugin
elif json_log["action"] == "processterminal":
pid = log["processid"]
current_process = process.get_process_by_pid(pid)
if current_process is not None:
plugin.dispath_process_terminal(
host, current_process, raw_log, json_log)
current_process.active = False
current_process.chain.terminate_count += 1
if current_process.chain.terminate_count >= (
current_process.chain.active_count - 1
):
current_process.chain.active = False
if current_process.chain.risk_score >= config.MAX_THREAT_SCORE:
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,
True,
)
process.g_ProcessChainList.remove(current_process.chain)
elif "processid" in log:
current_process = process.get_process_by_pid(log["processid"])
if current_process is not None:
log["action"] = json_log["action"]
had_threat, is_ioa, rule_hit_name, score = match_threat(
current_process, log, LOG_TYPE_PROCESS_ACTION)
had_threat_plugin = plugin.dispath_rule_new_process_action(
host, current_process, raw_log, json_log
)
if had_threat == global_vars.THREAT_TYPE_NONE:
had_threat = had_threat_plugin
update_process_threat_status(current_process, host, had_threat)
parent_pid = 0
target_pid = 0
self_hash = ""
@@ -170,6 +244,12 @@ def process_log(host, json_log, raw_log):
target_image_path = target_process.path
target_hash = target_process.md5
self_hash = current_process.md5
# 以后有其他排除需求再优化
# if json_log['action'] == 'imageload' and (json_log['data']['imageloaded'][len(json_log['data']['imageloaded']) - 4:] == '.exe' or json_log['data']['imageloaded'] in hash_white_list.g_white_dll_load_list):
# return
if json_log['action'] == 'imageload':
return
sql.push_process_raw(
host,
@@ -186,10 +266,107 @@ def process_log(host, json_log, raw_log):
params,
user,
)
"""
'''
for iter in process.g_ProcessChainList:
item: process.Process = iter
if item.risk_score >= config.MAX_THREAT_SCORE:
item.print_process()
"""
'''
def process_raw_log(raw_logs: list) -> list:
return_data = []
process_chain_list = []
raw_logs.sort(key=operator.attrgetter("timestamp"))
def _get_process_chain(pid, host: str) -> process.ProcessChain:
for iter in process_chain_list:
chain_item: process.ProcessChain = iter
if chain_item.host != host:
continue
process_item = chain_item.find_process_by_pid(pid)
if process_item is not None:
return chain_item
return None
for log in raw_logs:
log: sql.raw_process_log = log
pid = log.pid
ppid = log.ppid
path = log.path
params = log.commandline
user = log.user
hash = log.hash
create_time = log.timestamp
host = log.host
current_process: process.Process = None
if path in process.skip_process_path:
continue
if log.action.lower() == "processcreate":
chain = _get_process_chain(pid, host)
if chain is not None:
parent_process = chain.find_process_by_pid(ppid)
else:
parent_process = None
if chain is None:
# build a process chain
current_process = process.Process(
pid, ppid, path, params, create_time, hash, user, host
)
chain = process.create_chain(current_process)
process_chain_list.append(chain)
else:
current_process = process.Process(
pid, ppid, path, params, create_time, hash, user, host
)
chain.add_process(current_process, ppid)
elif log.action.lower() == "processterminal":
chain = _get_process_chain(pid, host)
if chain is not None:
current_process = chain.find_process_by_pid(pid)
current_process.active = False
current_process.chain.terminate_count += 1
if (
current_process.chain.terminate_count
>= current_process.chain.active_count
):
current_process.chain.active = False
else:
# 不在指定时段内被创建的进程的结束事件
continue
else:
chain = _get_process_chain(pid, host)
if chain is None:
continue
current_process = chain.find_process_by_pid(pid)
if current_process is None:
continue
# if current_process is None :
# breakpoint()
start_process = current_process.chain.root_process
start_process_info = {
"path": start_process.path,
"hash": start_process.md5,
"params": start_process.params,
"user": start_process.user,
"create_time": start_process.time,
}
return_data.append(
{
"host": current_process.host,
"chain_hash": current_process.chain.hash,
"hit_rule": log.hit,
"time": log.timestamp,
"type": log.type,
"risk_score": log.score,
"id": log.id,
"is_end": current_process.chain.active == False,
"start_process": start_process_info,
}
)
return return_data

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

@@ -1,7 +1,5 @@
import global_vars
import yara
import glob
from pathlib import Path
#import yara
rm_plugs_config = {
"enable": False,

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

@@ -0,0 +1,61 @@
import global_vars
import process
import hash_white_list
rm_plugs_config = {
"enable": True,
"author": "huoji",
"description": "检测mimikatz",
"version": "0.0.1"
}
mimikatz_dll_list = [
'c:\\windows\\system32\\advapi32.dll',
'c:\\windows\\system32\\crypt32.dll',
'c:\\windows\\system32\\cryptdll.dll',
'c:\\windows\\system32\\gdi32.dll',
'c:\\windows\\system32\\imm32.dll',
'c:\\windows\\system32\\msasn1.dll',
'c:\\windows\\system32\\msvcrt.dll',
'c:\\windows\\system32\\rpcrt4.dll',
'c:\\windows\\system32\\rsaenh.dll',
'c:\\windows\\system32\\samlib.dll',
'c:\\windows\\system32\\sechost.dll',
'c:\\windows\\system32\\secur32.dll',
'c:\\windows\\system32\\shell32.dll',
'c:\\windows\\system32\\shlwapi.dll',
'c:\\windows\\system32\\sspicli.dll',
'c:\\windows\\system32\\user32.dll',
'c:\\windows\\system32\\vaultcli.dll',
]
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' 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
def rule_new_process_action(current_process: process.Process, host, raw_log_data, json_log_data):
global mimikatz_dll_list
# 如果日志的action是imageload(dll加载)
if 'mimikatz_detected' in current_process.plugin_var and json_log_data['action'] == 'imageload' and current_process.plugin_var['mimikatz_detected'] == False:
# 把日志中的dll路径取出来
dll_path = json_log_data['data']['imageloaded']
# 如果dll的路径在mimikatz的路径里面,进程上下文+1
if dll_path in mimikatz_dll_list:
current_process.plugin_var['mimikatz_matched_num'] += 1
if current_process.plugin_var['mimikatz_matched_num'] >= len(mimikatz_dll_list):
current_process.set_score(300, "[mimikatz]检测到疑似mimikatz进程")
current_process.plugin_var['mimikatz_detected'] = True
return global_vars.THREAT_TYPE_PROCESS
return global_vars.THREAT_TYPE_NONE
def rule_init():
pass
def plugin_init():
print('mimikatz检测插件 2022/9/5 by huoji')

View File

@@ -0,0 +1,49 @@
import global_vars
import process
#import yara
import hash_white_list
rm_plugs_config = {
"enable": True,
"author": "huoji",
"description": "基于进程链的uac提权检测",
"version": "0.0.1"
}
def intergritylevel_to_int(str_name):
if str_name == 'high':
return 3
elif str_name == 'medium':
return 2
return 1
def rule_new_process_create(current_process: process.Process, host, raw_log_data, json_log_data):
if 'integritylevel' in json_log_data['data']:
integritylevel = intergritylevel_to_int(
json_log_data['data']['integritylevel'])
current_process.plugin_var['uac_flag'] = integritylevel
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'] 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
current_process.set_score(30, "进程权限等级变动")
return global_vars.THREAT_TYPE_PROCESS
# print('process chain: {} path: {} level: {} log level: {}'.format(
# current_process.chain_hash, current_process.path, integritylevel, current_process.chain.root_process.plugin_var['uac_flag']))
return global_vars.THREAT_TYPE_NONE
def rule_new_process_action(current_process, host, raw_log_data, json_log_data):
return global_vars.THREAT_TYPE_NONE
def rule_init():
pass
def plugin_init():
print('uac提权插件 2022/8/15 by huoji')

View File

@@ -1,8 +1,6 @@
import json
from sqlalchemy import false
import tools
import time
skip_process_path = ['c:\\program files\\rivet networks\\smartbyte\\raps.exe',
'c:\\program files (x86)\\sogouinput\\11.5.0.5352\\pinyinup.exe',
@@ -87,14 +85,17 @@ g_ProcessChainList = []
class Process:
def __init__(self, pid, ppid, path, params, time, md5, user, host):
def __init__(self, pid, ppid, path, params, time, md5, user, host, is_white=False):
self.pid = pid
self.parent_process = None
self.ppid = ppid
self.path = path
self.params = params
self.chain_hash = ''
self.active = True
self.operationlist = {}
self.attck_hit_list = {}
self.risk_score = 0
self.terminate = False
self.rmpid = tools.get_md5(
@@ -102,10 +103,12 @@ class Process:
self.time = time
self.rmppid = ""
self.root_rmpid = ""
self.plugin_var = {}
self.md5 = md5
self.user = user
self.chain: ProcessChain = None
self.host = host
self.is_white = is_white
def set_chain_data(self, chain):
self.chain = chain
@@ -119,6 +122,15 @@ class Process:
def set_rmppid(self, rmppid):
self.rmppid = rmppid
def set_attck(self, new_score, t, name):
if t not in self.attck_hit_list:
self.risk_score += new_score
self.attck_hit_list[t] = name
if t not in self.chain.attck_hit_list:
self.chain.risk_score += new_score
self.chain.attck_hit_list[t] = name
def set_score(self, new_score, opertion):
if opertion not in self.operationlist:
self.risk_score += new_score
@@ -143,6 +155,7 @@ class ProcessChain:
self.terminate_count = 0
self.risk_score = 0
self.operationlist = {}
self.attck_hit_list = {}
self.process_list = []
self.json_arrays = []
self.active = True
@@ -150,6 +163,7 @@ class ProcessChain:
self.rpc_process_chain = ""
self.time = root_process.time
self.host = root_process.host
self.plugin_var = {}
self.add_root_process(root_process)
def get_operationlist(self):
@@ -230,6 +244,7 @@ class ProcessChain:
"rmppid": proc_info.rmppid,
"params": proc_info.params,
"operationlist": proc_info.operationlist,
"attck_hit_list": proc_info.attck_hit_list,
"md5": proc_info.md5,
"active": proc_info.active,
"children": []

View File

@@ -1,82 +1,132 @@
import rule_engine
import rules.py.process as rule_process
import rules.py.action as rule_action
import rules.py.attck.process as attck_process
import rules.py.attck.attck as attack_software
import rules.py.attck.action as attack_action
import rules.py.ioa.action as ioa_action
import rules.py.ioa.process as ioa_process
import plugin
g_sample_rule = {}
g_sample_rule['process'] = rule_process.rule
g_sample_rule['action'] = rule_action.rule
g_sample_rule['attack_process'] = attck_process.rule
g_sample_rule['attack_action'] = attack_action.rule
g_sample_rule['attack_software'] = attack_software.rule
g_sample_rule['ioa_action'] = ioa_action.rule
g_sample_rule['ioa_process'] = ioa_process.rule
attck_process_rules = []
attck_action_rules = []
ioa_process_rules = []
ioa_action_rules = []
base_process_rules = []
base_action_rules = []
base_host_rules = []
def calc_score_in_action(log):
global base_action_rules
for iter in base_action_rules:
for rule in iter['rules']:
# 这是or
try:
if rule.matches(log):
return iter['score'], iter['name']
except:
print("error: {} ".format(log))
def match_att_ck_software(t_list):
# 返回是否命中,命中命中,分数
return 0, ''
global g_sample_rule
is_match = False
match_name = ''
match_score = 0
for iter in g_sample_rule['attack_software']:
rule_list = iter['rules']
min_match_num = iter['hit_num']
match_num = 0
for t in t_list.keys():
if t in rule_list:
match_num += 1
if match_num >= min_match_num:
is_match = True
match_name = iter['name']
match_score = iter['score']
break
if is_match:
break
return is_match, match_name, match_score
def calc_score_in_action(log):
# 返回 是否匹配到,是否ioa,attck,分数,名字
global attck_action_rules
global ioa_action_rules
for iter in ioa_action_rules:
for rule in iter['rules']:
if rule.matches(log):
return True, True, iter['attck_hit'], iter['score'], iter['name']
for iter in attck_action_rules:
for rule in iter['rules']:
if rule.matches(log):
return True, False, iter['attck_hit'], iter['score'], iter['name']
return False, False, [], 0, ''
def calc_score_in_create_process(log):
global base_process_rules
for iter in base_process_rules:
# 返回 是否匹配到,是否ioa,attck,分数,名字
global ioa_process_rules
global attck_process_rules
for iter in ioa_process_rules:
for rule in iter['rules']:
# 这是or
if rule.matches(log):
return iter['score'], iter['name']
return 0, ''
def calc_score_in_host(log):
global base_host_rules
for iter in base_host_rules:
return True, True, iter['attck_hit'], iter['score'], iter['name']
for iter in attck_process_rules:
for rule in iter['rules']:
# 这是or
if rule.matches(log):
return iter['score'], iter['name']
return 0, ''
return True, False, iter['attck_hit'], iter['score'], iter['name']
return False, False, [], 0, ''
def init_rule():
global base_process_rules
global base_action_rules
global base_host_rules
for iter in g_sample_rule['process']:
global attck_process_rules
global attck_action_rules
global ioa_process_rules
global ioa_action_rules
for iter in g_sample_rule['attack_process']:
temp_process_rules = []
score = 0
if 'score' not in iter:
score = 5
else:
score = iter['score']
for iter_i in iter['rules']:
print(iter_i)
print('rule: {} score: {}'.format(iter_i, score))
temp_process_rules.append(rule_engine.Rule(
iter_i
))
base_process_rules.append(
{'name': iter['name'], 'score': iter['score'], 'rules': temp_process_rules})
for iter in g_sample_rule['action']:
attck_process_rules.append(
{'name': iter['name'], 'attck_hit': iter['attck_hit'], 'score': score, 'rules': temp_process_rules})
for iter in g_sample_rule['attack_action']:
temp_process_rules = []
score = 0
if 'score' not in iter:
score = 5
else:
score = iter['score']
for iter_i in iter['rules']:
print(iter_i)
print('rule: {} score: {}'.format(iter_i, score))
temp_process_rules.append(rule_engine.Rule(
iter_i
))
base_action_rules.append(
{'name': iter['name'], 'score': iter['score'], 'rules': temp_process_rules})
'''
for iter in g_sample_rule['host']:
attck_action_rules.append(
{'name': iter['name'], 'attck_hit': iter['attck_hit'], 'score': score, 'rules': temp_process_rules})
for iter in g_sample_rule['ioa_action']:
temp_process_rules = []
for iter_i in iter['rules']:
print(iter_i)
print('rule: {} score: {}'.format(iter_i, score))
temp_process_rules.append(rule_engine.Rule(
iter_i
))
base_host_rules.append(
{'name': iter['name'], 'score': iter['score'], 'rules': temp_process_rules})
'''
ioa_action_rules.append(
{'name': iter['name'], 'attck_hit': iter['attck_hit'], 'score': iter['score'], 'rules': temp_process_rules})
for iter in g_sample_rule['ioa_process']:
temp_process_rules = []
for iter_i in iter['rules']:
print('rule: {} score: {}'.format(iter_i, score))
temp_process_rules.append(rule_engine.Rule(
iter_i
))
ioa_process_rules.append(
{'name': iter['name'], 'attck_hit': iter['attck_hit'], 'score': iter['score'], 'rules': temp_process_rules})
plugin.dispath_rule_init()
print('init rule done')

View File

@@ -1,4 +1,11 @@
rule = [
{
'rules': [
'action == "filecreate" and targetfilename =~ "c:\\users\\.*\\appdata\\roaming\\microsoft\\outlook\\vbaproject.otm"'
],
'score': 300,
'name': '已知Outlook模板宏持久化行为'
},
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe" and grantedaccess & 0x0010 and sourceimage =~ ".*rundll32.exe"',
@@ -17,7 +24,7 @@ rule = [
'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"',
],
'score': 40,
'score': 20,
'name': '异常进程访问'
},
{
@@ -161,5 +168,12 @@ rule = [
],
'score': 50,
'name': '创建可疑文件'
},
{
'rules': [
'action == "imageload" and imageloaded == "c:\\windows\\system32\\samlib.dll"',
],
'score': 10,
'name': 'samlib的dll被加载'
}
]

View File

@@ -0,0 +1,165 @@
rule = [
{
'rules': [
'action == "registryvalueset" and targetobject =~ ".*proxyenable"',
],
'attck_hit':['T1562.001'],
'name': 'Impair Defenses: Disable or Modify Tools'
},
{
'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"',
],
'attck_hit':['T1620'],
'name': 'Reflective Code Loading'
},
{
'rules': [
'action == "processaccess" and calltrace =~ ".*wshom\.ocx.*"',
'action == "processaccess" and calltrace =~ ".*shell32\.dll.*"',
'action == "processaccess" and calltrace =~ ".*dbgcore\.dll.*"',
'action == "processaccess" and calltrace =~ ".*kernelbase\.dll\+de67e.*"',
'action == "processaccess" and calltrace =~ ".*framedynos\.dll.*"',
],
'attck_hit':['T1559.001'],
'name': 'Inter-Process Communication: Component Object Model'
},
# todo 懒得做详细的规则了.加油完善规则吧
{
'rules': [
'action == "createremotethread"',
],
'attck_hit':['T1055'],
'score': 30,
'name': 'Process Injection'
},
{
'rules': [
'action == "filecreatestreamhash"',
],
'attck_hit':['T1564.004'],
'name': 'Hide Artifacts: NTFS File Attributes'
},
{
'rules': [
'action == "dnsquery"',
],
'attck_hit':['T1071.004'],
'name': 'Application Layer Protocol: DNS'
},
{
'rules': [
'action == "filecreatetimechange"',
],
'attck_hit':['T1070.006'],
'name': 'Indicator Removal on Host: Timestomp'
},
{
'rules': [
'action == "networkconnect"',
],
'attck_hit':['T1071'],
'name': 'Application Layer Protocol'
},
{
'rules': [
'action == "clipboardchange"',
],
'attck_hit':['T1115'],
'name': 'Clipboard Data Monitor API'
},
{
'rules': [
'action == "processtampering"',
],
'attck_hit':['T1574'],
'name': 'Hijack Execution Flow'
},
{
'rules': [
'action == "filecreate" and targetfilename =~ "c:\\\\\\\\windows\\\\\\\\.*"',
'action == "filecreate" and targetfilename =~ ".*\.exe"',
'action == "filecreate" and targetfilename =~ ".*\.cmd"',
'action == "filecreate" and targetfilename =~ ".*\.bat"',
'action == "filecreate" and targetfilename =~ ".*\.dll"',
],
'attck_hit':['T1036.005'],
'name': 'Masquerading: Match Legitimate Name or Location'
},
{
'rules': [
'action == "filecreate" and targetfilename =~ "c:\\\\\\\\windows\\\\\\\\.*"',
],
'attck_hit':['T1036.005'],
'name': 'Masquerading: Match Legitimate Name or Location'
},
{
'rules': [
'action == "filecreate" and targetfilename =~ "c:\\\\\\\\users\\\\\\\\.*"',
'action == "filecreate" and targetfilename =~ ".*\.exe"',
'action == "filecreate" and targetfilename =~ ".*\.cmd"',
'action == "filecreate" and targetfilename =~ ".*\.bat"',
'action == "filecreate" and targetfilename =~ ".*\.dll"',
],
'attck_hit':['T1036.005'],
'name': 'Masquerading: Match Legitimate Name or Location'
},
{
'rules': [
'action == "imageload" and imageloaded == "c:\\windows\\system32\\samlib.dll"',
],
'attck_hit':['T1003.002'],
'name': 'OS Credential Dumping: Security Account Manager'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*credui.dll"',
],
'attck_hit':['T1047'],
'name': 'Windows Management Instrumentation'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*dbghelp.dll"',
],
'attck_hit':['T1622'],
'name': 'Debugger Evasion'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*winhttp.dll"',
'action == "imageload" and imageloaded =~ ".*urlmon.dll"',
],
'attck_hit':['T1071.001'],
'name': 'Application Layer Protocol: Web Protocols'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*dnsapi.dll"',
],
'attck_hit':['T1071.004'],
'name': 'Application Layer Protocol: DNS'
},
# 不应该用dll来当T的,这里应该是api的hook.但是sysmon没这些ds,只能凑合.这非常不专业
{
'rules': [
'action == "imageload" and imageloaded =~ ".*rtutils.dll"',
],
'attck_hit':['CMT0001'],
'name': 'Event trace manipulation'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*rasapi32.dll"',
],
'attck_hit':['CMT0002'],
'name': 'rasapi32 manipulation'
},
{
'rules': [
'action == "imageload" and imageloaded =~ ".*napinsp.dll"',
],
'attck_hit':['CMT0003'],
'name': 'napinsp manipulation'
}
]

View File

@@ -0,0 +1,12 @@
rule = [
{'name': "BRC4", 'rules': ['T1071', 'T1071.001',
'T1622', 'T1047', 'T1562.001'], 'hit_num': 4, 'score':100},
{'name': "BRC4#2", 'rules': ['T1071.004',
'T1071.001', 'T1562.001', 'CMT0001', 'CMT0002', 'CMT0003'], 'hit_num': 6, 'score':100},
{'name': "Ransomware", 'rules': ['T1071',
'T1036.005', 'T1620', 'T1564.001', 'T1222.001', 'T1059.005', 'T1543.003', 'T1490'], 'hit_num': 7, 'score':100},
{'name': "APT-System discovery", 'rules': ['T1018',
'T1087.001', 'T1087.001', 'T1082', 'T1016'], 'hit_num': 3, 'score':65},
{'name': "APT-Hydra", 'rules': ['T1027.004',
'T1018', 'T1559.001', 'T1218.011', 'T1059.001', 'T1059.005', 'T1570', 'T1087.002', 'T1564', 'T1106', 'T1082', 'T1087.001', 'T1003', 'T1071'], 'hit_num': 10, 'score':100}
]

View File

@@ -0,0 +1,314 @@
rule = [
{
'rules': [
'originalfilename == "taskill.exe"',
'originalfilename == "net.exe" and commandline =~ ".*stop.*"',
'originalfilename == "sc.exe" and commandline =~ ".*config.*" and commandline =~ ".*disabled.*"',
],
'attck_hit':['T1489'],
'score': 30,
'name': 'Service Stop'
},
{
'rules': [
'originalfilename =~ ".*curl.exe" or originalfilename =~ ".*wget.exe" or originalfilename =~ ".*dget.exe"',
'originalfilename =~ ".*certutil.exe"',
'originalfilename =~ ".*powershell.exe" and commandline =~ ".*invoke-webrequest.*"'
],
'attck_hit':['T1105'],
'score': 30,
'name':'Ingress Tool Transfer'
},
{
'rules': [
'image =~ ".*\.doc\.exe"',
'image =~ ".*\.docx\.exe"',
'image =~ ".*\.ppt\.exe"',
'image =~ ".*\.pdf\.exe"',
'image =~ ".*\.html\.exe"',
'image =~ ".*\.htm\.exe"',
'image =~ ".*\.zip\.exe"',
'image =~ ".*\.rar\.exe"'
],
'attck_hit':['T1036.007'],
'score': 60,
'name':'Masquerading: Double File Extension'
},
{
'rules': [
'commandline =~ ".*-k dcomlaunch.*"'
],
'attck_hit':['T1559.001'],
'score': 30,
'name':'Inter-Process Communication: Component Object Model'
},
{
'rules': [
'originalfilename =~ ".*vssadmin.exe" and commandline =~ ".*create.*"',
],
'attck_hit':['T1003.003'],
'score': 30,
'name':'OS Credential Dumping: NTDS'
},
{
'rules': [
'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.*"',
'originalfilename =~ ".*vssadmin.exe" and commandline =~ ".*shadows.*" and commandline =~ ".*delete.*"',
],
'attck_hit':['T1490'],
'score': 30,
'name': 'Inhibit System Recovery'
},
{
'rules': [
'originalfilename == "net.exe" and commandline =~ ".*view.*"',
'originalfilename == "net.exe" and commandline =~ ".*group.*"',
'originalfilename == "ping.exe"',
],
'attck_hit':['T1018'],
'score': 10,
'name': 'Remote System Discovery'
},
{
'rules': [
'originalfilename =~ ".*fsutil.exe" and commandline =~ ".*deletejournal.*"',
],
'attck_hit':['T1070.004'],
'score': 10,
'name': 'Indicator Removal on Host'
},
{
'rules': [
'originalfilename == ".*net.exe" and commandline =~ ".*user.*"',
'originalfilename =~ ".*whoami.exe"',
'originalfilename =~ ".*query.exe"',
'originalfilename =~ ".*setspn.exe"',
'originalfilename =~ ".*cmdkey.exe"'
],
'attck_hit':['T1087.001'],
'score': 30,
'name': 'Account Discovery: Local Account'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*useraccount.*"',
],
'attck_hit':['T1087.001', 'T1047'],
'score': 30,
'name': 'Account Discovery: Local Account by wmic'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*startup.*"',
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*share.*"',
],
'attck_hit':['T1082', 'T1047'],
'score': 30,
'name': 'System Information Discovery by wmic'
},
{
'rules': [
'originalfilename =~ ".*systeminfo.exe"',
'originalfilename =~ ".*chcp.com"'
],
'attck_hit':['T1082'],
'score': 10,
'name': 'System Information Discovery'
},
{
'rules': [
'originalfilename =~ ".*tasklist.exe"',
],
'attck_hit':['T1057'],
'score': 10,
'name': 'Process Discovery'
},
{
'rules': [
'originalfilename == "at.exe"',
],
'attck_hit':['T1053.002'],
'score': 10,
'name': 'Scheduled Task/Job: at'
},
{
'rules': [
'originalfilename =~ ".*schtasks.exe.*"',
],
'attck_hit':['T1053.005'],
'score': 10,
'name': 'Scheduled Task/Job: Scheduled Task'
},
{
'rules': [
'image =~ ".*\\\\\\\\appdata\\\\\\\\local\\\\\\\\temp\\\\\\\\.*" or image =~ ".*\\\\\\\\windows\\\\\\\\temp\\\\\\\\.*"',
],
'attck_hit':['T1106'],
'score': 10,
'name': 'Execution: Native API'
},
{
'rules': [
'originalfilename =~ ".*rubeus.*" and commandline =~ ".*domain.*"',
],
'attck_hit':['T1558.003'],
'score': 10,
'name': 'Steal or Forge Kerberos Tickets: Kerberoasting'
},
{
'rules': [
'originalfilename =~ ".*\u202e.*"',
],
'attck_hit':['T1564'],
'score': 10,
'name': 'Hide Artifacts'
},
{
'rules': [
'parentimage =~ ".*mmc.exe" and commandline =~ ".*eventvwr\.msc.*"',
],
'attck_hit':['T1218.014'],
'score': 10,
'name': 'System Binary Proxy Execution: MMC'
},
{
'rules': [
'originalfilename == "net.exe" and commandline =~ ".*domain.*"',
'originalfilename == "net.exe" and commandline =~ ".*view.*"',
'originalfilename == "net.exe" and commandline =~ ".*workstation.*"'
],
'attck_hit':['T1087.002'],
'score': 10,
'name': 'Account Discovery: Domain Account'
},
{
'rules': [
'originalfilename == "netsh.exe" and commandline =~ ".*firewall.*"',
],
'attck_hit':['T1562.004'],
'score': 10,
'name': 'Impair Defenses: Disable or Modify System Firewall'
},
{
'rules': [
'originalfilename =~ ".*ipconfig.exe"',
'originalfilename =~ ".*netstat.exe"'
],
'attck_hit':['T1016'],
'score': 10,
'name': 'System Network Configuration Discovery'
},
{
'rules': [
'originalfilename =~ ".*attrib.exe"',
],
'attck_hit':['T1564.001'],
'score': 10,
'name': 'Hide Artifacts: Hidden Files and Directories'
},
{
'rules': [
'originalfilename =~ ".*psexesvc.exe"',
],
'attck_hit':['T1570'],
'score': 10,
'name': 'Lateral Tool Transfer'
},
{
'rules': [
'originalfilename =~ "\\\\\\\\.*\\\\\\C\$.*"',
],
'attck_hit':['T1080'],
'score': 10,
'name': 'Taint Shared Content'
},
{
'rules': [
'originalfilename =~ ".*icacls.exe"',
],
'attck_hit':['T1222.001'],
'score': 10,
'name': 'Windows File and Directory Permissions Modification'
},
{
'rules': [
'parentimage =~ ".*services.exe"',
],
'attck_hit':['T1543.003'],
'score': 10,
'name': 'Create or Modify System Process: Windows Service'
},
{
'rules': [
'originalfilename =~ ".*werfault.exe" and parentimage =~ ".*svchost.exe"',
],
'attck_hit':['T1218'],
'score': 10,
'name': 'System Binary Proxy Execution'
},
{
'rules': [
'originalfilename =~ ".*wscript.exe"',
'originalfilename =~ ".*cscript.exe"',
],
'attck_hit':['T1059.005'],
'score': 10,
'name': 'Command and Scripting Interpreter: Visual Basic'
},
{
'rules': [
'originalfilename =~ ".*mofcomp.exe.*"'
],
'attck_hit':['T1546.015'],
'score': 10,
'name':'Event Triggered Execution: Component Object Model Hijacking'
},
{
'rules': [
'originalfilename =~ ".*csc.exe.*"'
],
'attck_hit':['T1027.004'],
'score': 10,
'name':'Compile After Delivery'
},
# https://attack.mitre.org/software/S0552/
{
'rules': [
'originalfilename =~ ".*adfind.exe.*"'
],
'attck_hit':['T1018'],
'score': 10,
'name':'Remote System Discovery'
},
{
'rules': [
'originalfilename == "wmic.exe"'
],
'attck_hit':['T1559.001'],
'score': 30,
'name':'Windows Management Instrumentation'
},
{
'rules': [
'originalfilename =~ ".*rundll32.exe.*"'
],
'attck_hit':['T1218.011'],
'score': 10,
'name':'System Binary Proxy Execution: Rundll32'
},
{
'rules': [
'originalfilename =~ ".*powershell.exe"'
],
'attck_hit':['T1059.001'],
'score': 10,
'name':'Command and Scripting Interpreter: PowerShell'
},
]

View File

@@ -0,0 +1,58 @@
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"',
],
'attck_hit':['T1003.002'],
'score': 100,
'name': '已知内存加载mimikazt行为'
},
{
'rules': [
'action == "processaccess" and sourceimage =~ ".*office16.*" and calltrace =~ ".*kernelbase\.dll.*"',
],
'attck_hit':['T1003.002'],
'score': 60,
'name': 'office异常进程内存'
},
{
'rules': [
'action == "pipecreate" and pipename =~ ".*msagent.*"',
'action == "pipecreate" and pipename =~ ".*msse.*"',
'action == "pipecreate" and pipename =~ ".*postex_.*"',
'action == "pipecreate" and pipename =~ ".*postex_ssh.*"',
'action == "pipecreate" and pipename =~ ".*status_.*"',
],
'attck_hit':['T1003.002'],
'score': 100,
'name': '已知CobalStrike'
},
{
'rules': [
'action == "pipecreate" and pipename =~ ".*paexec.*"',
'action == "pipecreate" and pipename =~ ".*remcom.*"',
'action == "pipecreate" and pipename =~ ".*csexec.*"'
],
'attck_hit':['T1003.002'],
'score': 100,
'name': '已知内网横向工具'
},
{
'rules': [
'action == "pipecreate" and pipename =~ ".*lsadump.*"',
'action == "pipecreate" and pipename =~ ".*cachedump.*"',
'action == "pipecreate" and pipename =~ ".*wceservicepipe.*"'
],
'attck_hit':['T1003.002'],
'score': 100,
'name': '已知mimikazt内存dump'
},
]

View File

@@ -0,0 +1,35 @@
rule = [
{
'rules': [
'originalfilename =~ ".*todesk_service.*" or originalfilename =~ ".*sunloginclient.*" or originalfilename =~ ".*teamviewer_service.exe.*" or originalfilename =~ ".*logmein.*" or originalfilename =~ ".*dwrcs.*" or originalfilename =~ ".*aa_v3.*" or originalfilename =~ ".*screenconnect.*" or originalfilename =~ ".*tvnserver.*" or originalfilename =~ ".*vncserver.*"',
],
'attck_hit':['T1133'],
'score': 30,
'name': '已知远程协助程序'
},
{
'rules': [
'originalfilename =~ ".*phoenixminer.*" or originalfilename =~ ".*ccminer.*" or originalfilename =~ ".*csminer.exe.*" or originalfilename =~ ".*xmrig.*" or originalfilename =~ ".*xmr-stak.*"',
],
'attck_hit':['T1496'],
'score': 100,
'name': '已知挖矿程序'
},
{
'rules': [
'originalfilename =~ "\\\\\\.*" and parentimage =~ ".*services.exe"',
],
'attck_hit':['T1021.006'],
'score': 100,
'name': '远程服务被创建'
},
{
'rules': [
'commandline =~ ".*__\d{10}\."',
'originalfilename =~ ".*wmi_share.exe"',
],
'attck_hit':['T00000'],
'score': 100,
'name': 'wmic内网横向移动被触发'
},
]

View File

@@ -1,390 +0,0 @@
rule = [
{
'rules': [
'originalfilename =~ ".*taskill.exe.*"',
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*stop.*"',
'originalfilename =~ ".*sc.exe.*" and commandline =~ ".*config.*" and commandline =~ ".*disabled.*"',
],
'score': 40,
'name': '通过系统程序关闭进程'
},
{
'rules': [
'originalfilename =~ ".*curl.exe" or originalfilename =~ ".*wget.exe" or originalfilename =~ ".*dget.exe"'
],
'score': 40,
'name':'通过应用下载文件'
},
{
'rules': [
'image =~ ".*\.doc\.exe"',
'image =~ ".*\.docx\.exe"',
'image =~ ".*\.ppt\.exe"',
'image =~ ".*\.pdf\.exe"',
'image =~ ".*\.html\.exe"',
'image =~ ".*\.htm\.exe"',
'image =~ ".*\.zip\.exe"',
'image =~ ".*\.rar\.exe"'
],
'score': 30,
'name':'启动双扩展名文件'
},
{
'rules': [
'commandline =~ ".*-k dcomlaunch.*"'
],
'score': 30,
'name':'通过DCOM启动了进程'
},
{
'rules': [
'originalfilename =~ ".*wbadmin.exe.*" and commandline =~ ".*delete.*"',
],
'score': 70,
'name': '通过wbadmin删除备份'
},
{
'rules': [
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*view.*"',
],
'score': 70,
'name': '通过net进行远程系统发现'
},
{
'rules': [
'originalfilename =~ ".*fsutil.exe.*" and commandline =~ ".*deletejournal.*"',
],
'score': 70,
'name': '通过系统工具删除USN'
},
{
'rules': [
'originalfilename =~ ".*net.exe.*" and commandline =~ ".*user.*"',
],
'score': 70,
'name': '通过net进行系统用户发现'
},
{
'rules': [
'originalfilename =~ ".*schtasks.exe.*" and commandline =~ ".*create.*"',
],
'score': 70,
'name': '通过系统应用创建计划任务'
},
{
'rules': [
'originalfilename =~ ".*schtasks.exe.*" and commandline =~ ".*delete.*"',
],
'score': 40,
'name': '通过系统应用删除计划任务'
},
{
'rules': [
'originalfilename =~ ".*vssadmin.exe.*" and commandline =~ ".*create.*"',
],
'score': 40,
'name': '通过系统程序创建卷影备份'
},
{
'rules': [
'originalfilename =~ ".*todesk_service.*" or originalfilename =~ ".*sunloginclient.*" or originalfilename =~ ".*teamviewer_service.exe.*" or originalfilename =~ ".*logmein.*" or originalfilename =~ ".*dwrcs.*" or originalfilename =~ ".*aa_v3.*" or originalfilename =~ ".*screenconnect.*" or originalfilename =~ ".*tvnserver.*" or originalfilename =~ ".*vncserver.*"',
],
'score': 20,
'name': '已知远程协助程序'
},
{
'rules': [
'originalfilename =~ ".*phoenixminer.*" or originalfilename =~ ".*ccminer.*" or originalfilename =~ ".*csminer.exe.*" or originalfilename =~ ".*xmrig.*" or originalfilename =~ ".*xmr-stak.*"',
],
'score': 300,
'name': '已知挖矿程序'
},
{
'rules': [
'image =~ ".*\\\\\\\\appdata\\\\\\\\local\\\\\\\\temp\\\\\\\\.*" or image =~ ".*\\\\\\\\windows\\\\\\\\temp\\\\\\\\.*"',
],
'score': 40,
'name': '从临时文件创建进程'
},
{
'rules': [
'originalfilename =~ ".*rubeus.*" and commandline =~ ".*domain.*"',
],
'score': 100,
'name': '通过系统工具获取域登陆令牌'
},
{
'rules': [
'originalfilename =~ ".*whoami.*"',
],
'score': 70,
'name': 'whoami被执行'
},
{
'rules': [
'originalfilename =~ ".*\u202e.*"',
],
'score': 100,
'name': '伪装名字程序被执行'
},
{
'rules': [
'parentimage =~ ".*mmc.exe" and commandline =~ ".*eventvwr\.msc.*"',
],
'score': 40,
'name': '高权限进程被创建'
},
{
'rules': [
'originalfilename =~ ".*bcdedit.exe" and commandline =~ ".*recoveryenabled.*no.*"',
'originalfilename =~ ".*bcdedit.exe" and commandline =~ ".*bootstatuspolicy.*ignoreallfailures.*"',
],
'score': 80,
'name': '通过系统工具关闭系统恢复'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*useraccount.*"',
],
'score': 70,
'name': '通过wmic进行系统用户发现'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*startup.*"',
],
'score': 70,
'name': '通过wmic查看系统启动项'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*share.*"',
],
'score': 70,
'name': '通过wmic查看系统共享'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe" and commandline =~ ".*shadowcopy.*" and commandline =~ ".*delete.*"',
],
'score': 70,
'name': 'wmic删除卷影备份'
},
{
'rules': [
'originalfilename =~ ".*vssadmin.exe" and commandline =~ ".*shadows.*" and commandline =~ ".*delete.*"',
],
'score': 70,
'name': 'vssadmin删除卷影备份'
},
{
'rules': [
'originalfilename =~ ".*tasklist.exe"',
],
'score': 50,
'name': '通过tasklist查看系统信息'
},
{
'rules': [
'originalfilename =~ ".*systeminfo.exe"',
],
'score': 70,
'name': '通过systeminfo查看系统信息'
},
{
'rules': [
'originalfilename =~ ".*query.exe"',
],
'score': 70,
'name': '通过query进行系统用户发现'
},
{
'rules': [
'originalfilename =~ ".*net.exe" and commandline =~ ".*domain.*"',
'originalfilename =~ ".*net.exe" and commandline =~ ".*view.*"',
'originalfilename =~ ".*net.exe" and commandline =~ ".*workstation.*"'
],
'score': 70,
'name': '通过net进行本地系统用户发现'
},
{
'rules': [
'originalfilename =~ ".*setspn.exe"',
],
'score': 70,
'name': '通过setspn进行本地系统用户发现'
},
{
'rules': [
'originalfilename =~ ".*netsh.exe" and commandline =~ ".*firewall.*"',
],
'score': 70,
'name': '通过netsh关闭防火墙'
},
{
'rules': [
'originalfilename =~ ".*cmd.exe" and commandline =~ ".*ipconfig.*"',
],
'score': 80,
'name': 'cmd启动ipconfig'
},
{
'rules': [
'originalfilename =~ ".*cmd.exe" and commandline =~ ".*net.*"',
],
'score': 60,
'name': 'cmd启动net'
},
{
'rules': [
'originalfilename =~ ".*netstat.exe"',
],
'score': 40,
'name': 'netstat被运行'
},
{
'rules': [
'originalfilename =~ ".*ping.exe"',
],
'score': 40,
'name': 'ping被运行'
},
{
'rules': [
'originalfilename =~ ".*ipconfig.exe"',
],
'score': 40,
'name': 'ipconfig被运行'
},
{
'rules': [
'originalfilename =~ ".*attrib.exe"',
],
'score': 40,
'name': 'attrib被运行'
},
{
'rules': [
'originalfilename =~ ".*PSEXESVC.exe"',
],
'score': 100,
'name': 'PSEXESVC内网横向移动'
},
{
'rules': [
'originalfilename =~ "\\\\\\\\.*\\\\\\C\$.*"',
],
'score': 100,
'name': 'SMB共享启动进程'
},
{
'rules': [
'commandline =~ ".*__\d{10}\."',
'originalfilename =~ ".*wmi_share.exe"',
],
'score': 100,
'name': 'wmic内网横向移动被触发'
},
{
'rules': [
'originalfilename =~ ".*icacls.exe"',
],
'score': 40,
'name': 'icacls被运行'
},
{
'rules': [
'originalfilename =~ "\\\\\\.*" and parentimage =~ ".*services.exe"',
],
'score': 100,
'name': '远程服务被创建'
},
{
'rules': [
'parentimage =~ ".*services.exe"',
],
'score': 30,
'name': '从服务创建的进程'
},
{
'rules': [
'originalfilename =~ ".*wscript.exe"',
'originalfilename =~ ".*cscript.exe"',
],
'score': 40,
'name': '脚本程序被运行'
},
{
'rules': [
'originalfilename =~ ".*mofcomp.exe.*"'
],
'score': 80,
'name':'注册WMI订阅'
},
{
'rules': [
'originalfilename =~ ".*csc.exe.*"'
],
'score': 80,
'name':'.NET编译器被启动'
},
{
'rules': [
'originalfilename =~ ".*cmdkey.exe.*"'
],
'score': 100,
'name':'通过系统应用查询本机账户'
},
{
'rules': [
'originalfilename =~ ".*adfind.exe.*"'
],
'score': 80,
'name':'通过系统程序发现域信息'
},
# 这些是保底规则 必须放到最底下才匹配
{
'rules': [
'originalfilename =~ ".*cmd.exe"'
],
'score': 30,
'name':'执行CMD命令'
},
{
'rules': [
'originalfilename =~ ".*chcp.com"'
],
'score': 30,
'name':'执行chcp.com'
},
{
'rules': [
'originalfilename =~ ".*wmic.exe.*"'
],
'score': 80,
'name':'执行wmic'
},
{
'rules': [
'originalfilename =~ ".*rundll32.exe.*"'
],
'score': 20,
'name':'通过rundll32启动进程'
},
{
'rules': [
'originalfilename =~ ".*certutil.exe"',
'originalfilename =~ ".*curl.exe"',
'originalfilename =~ ".*powershell.exe" and commandline =~ ".*invoke-webrequest.*"'
],
'score': 80,
'name':'通过系统命令下载文件'
},
{
'rules': [
'originalfilename =~ ".*powershell.exe"'
],
'score': 80,
'name':'Powershell被执行'
},
]

View File

@@ -10,6 +10,7 @@ from sqlalchemy import Column, Integer, String, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import delete
import sqlalchemy
import json
g_engine = None
@@ -19,6 +20,8 @@ g_rawdata_table = None
g_rawdata_table_ins = None
g_threat_table = None
g_threat_table_ins = None
g_hash_white_list_table = None
g_hash_white_list_table_ins = None
class raw_process_log(g_base):
@@ -61,6 +64,15 @@ class raw_process_log(g_base):
return self.id
class hash_white_list(g_base):
__tablename__ = "hash_white_list"
id = Column(Integer, primary_key=True)
hash = Column(String)
path = Column(String)
timestamp = Column(Integer)
reason = Column(String)
class threat_log(g_base):
__tablename__ = "threat_log"
# 定义各字段
@@ -75,6 +87,8 @@ class threat_log(g_base):
risk_score = Column(Integer)
# 命中的规则
hit_rule = Column(String)
# attck命中
attck_hit_list = Column(String)
# json字段
data = Column(String)
# 时间戳
@@ -98,8 +112,11 @@ def init():
global g_rawdata_table_ins
global g_threat_table
global g_threat_table_ins
global g_hash_white_list_table
global g_hash_white_list_table_ins
g_engine = create_engine("sqlite:///syseye.db?check_same_thread=False", echo=False)
g_engine = create_engine(
"sqlite:///syseye.db?check_same_thread=False", echo=False)
g_base.metadata.create_all(g_engine)
g_metadata = MetaData(g_engine)
g_rawdata_table = Table("raw_process_log", g_metadata, autoload=True)
@@ -108,6 +125,56 @@ def init():
g_threat_table = Table("threat_log", g_metadata, autoload=True)
g_threat_table_ins = g_threat_table.insert()
g_hash_white_list_table = Table(
"hash_white_list", g_metadata, autoload=True)
g_hash_white_list_table_ins = g_hash_white_list_table.insert()
def query_white_list_by_hash(pHash):
global g_hash_white_list_table
sql_session = sessionmaker(bind=g_engine)
white_list = sql_session().query(
g_hash_white_list_table).filter_by(hash=pHash).first()
sql_session().close()
return white_list
def delete_white_list(pHash):
global g_hash_white_list_table
global g_engine
conn = g_engine.connect()
result = conn.execute(
delete(g_hash_white_list_table).where(
g_hash_white_list_table.columns.hash == pHash)
)
conn.close()
return result
def push_white_list(pPath, pHash, pReason):
global g_hash_white_list_table_ins
current_time = int(round(time.time() * 1000))
ins = g_hash_white_list_table_ins.values(
path=pPath, hash=pHash, reason=pReason, timestamp=current_time)
# 连接引擎
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
conn.close()
return result
def query_all_white_list():
global g_hash_white_list_table
sql_session = sessionmaker(bind=g_engine)
white_list = (
sql_session()
.query(g_hash_white_list_table)
.all()
)
sql_session().close()
return white_list
def push_process_raw(
host,
@@ -163,22 +230,43 @@ def push_process_raw(
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
conn.close()
return result
def select_create_process_raw_log_by_time(start, end):
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(g_rawdata_table)
.filter(
raw_process_log.timestamp >= start,
raw_process_log.timestamp < end,
raw_process_log.action == "processcreate",
)
.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)
# 用g_rawdata_table 不行, utf8编码问题
raw_log = (
sql_session()
.query(raw_process_log)
.filter(
sqlalchemy.and_(
raw_process_log.timestamp >= start, raw_process_log.timestamp < end
)
)
.all()
)
sql_session().close()
return raw_log
@@ -197,7 +285,7 @@ def select_threat_by_chain_id(host, process_chain_hash, type):
def update_threat_log(
host, risk_score, hit_rule_json, process_chain_hash, raw_json, type, is_end
host, risk_score, hit_rule_json, attck_hit_list_json, process_chain_hash, raw_json, type, is_end
):
global g_threat_table
global g_engine
@@ -207,6 +295,7 @@ def update_threat_log(
.values(
risk_score=risk_score,
hit_rule=hit_rule_json,
attck_hit_list=attck_hit_list_json,
data=raw_json,
is_end=int(is_end),
)
@@ -217,6 +306,7 @@ def update_threat_log(
)
)
result = conn.execute(update)
conn.close()
return result
@@ -230,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
@@ -238,8 +329,10 @@ def delete_threat(threat_id):
global g_engine
conn = g_engine.connect()
result = conn.execute(
delete(g_threat_table).where(g_threat_table.columns.id == int(threat_id))
delete(g_threat_table).where(
g_threat_table.columns.id == int(threat_id))
)
conn.close()
return result
@@ -251,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)
@@ -269,6 +378,7 @@ def query_all_threat_log(query_type):
threat_log.is_end,
threat_log.start_process_info,
threat_log.handle_type,
threat_log.attck_hit_list,
)
.all()
)
@@ -287,6 +397,7 @@ def query_all_threat_log(query_type):
threat_log.is_end,
threat_log.start_process_info,
threat_log.handle_type,
threat_log.attck_hit_list
)
.filter_by(handle_type=query_type)
.all()
@@ -299,6 +410,7 @@ def push_threat_log(
host,
risk_score,
hit_rule_json,
attck_hit_list_json,
process_chain_hash,
raw_json,
type,
@@ -312,6 +424,7 @@ def push_threat_log(
risk_score=risk_score,
process_chain_hash=process_chain_hash,
hit_rule=hit_rule_json,
attck_hit_list=attck_hit_list_json,
type=type,
data=raw_json,
timestamp=int(round(time.time() * 1000)),
@@ -323,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.070221f5.js></script><script defer src=js/app.3ea8aeff.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([[193],{2193:(e,t,s)=>{s.r(t),s.d(t,{default:()=>p});var l=s(3673);const n={class:"fullscreen bg-blue text-white text-center q-pa-md flex flex-center"},o=(0,l._)("div",{style:{"font-size":"30vh"}}," 404 ",-1),c=(0,l._)("div",{class:"text-h2",style:{opacity:".4"}}," Oops. Nothing here... ",-1);function a(e,t,s,a,r,i){const u=(0,l.up)("q-btn");return(0,l.wg)(),(0,l.iD)("div",n,[(0,l._)("div",null,[o,c,(0,l.Wm)(u,{class:"q-mt-xl",color:"white","text-color":"blue",unelevated:"",to:"/",label:"Go Home","no-caps":""})])])}const r=(0,l.aZ)({name:"Error404"});var i=s(4260),u=s(9400),h=s(7518),b=s.n(h);const d=(0,i.Z)(r,[["render",a]]),p=d;b()(r,"components",{QBtn:u.Z})}}]);
"use strict";(globalThis["webpackChunksyseye"]=globalThis["webpackChunksyseye"]||[]).push([[193],{2193:(e,t,s)=>{s.r(t),s.d(t,{default:()=>p});var l=s(3673);const n={class:"fullscreen bg-blue text-white text-center q-pa-md flex flex-center"},o=(0,l._)("div",{style:{"font-size":"30vh"}}," 404 ",-1),c=(0,l._)("div",{class:"text-h2",style:{opacity:".4"}}," Oops. Nothing here... ",-1);function a(e,t,s,a,r,i){const u=(0,l.up)("q-btn");return(0,l.wg)(),(0,l.iD)("div",n,[(0,l._)("div",null,[o,c,(0,l.Wm)(u,{class:"q-mt-xl",color:"white","text-color":"blue",unelevated:"",to:"/",label:"Go Home","no-caps":""})])])}const r=(0,l.aZ)({name:"Error404"});var i=s(4260),u=s(8240),h=s(7518),b=s.n(h);const d=(0,i.Z)(r,[["render",a]]),p=d;b()(r,"components",{QBtn:u.Z})}}]);

View File

@@ -0,0 +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(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

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

View File

@@ -1 +0,0 @@
"use strict";(globalThis["webpackChunksyseye"]=globalThis["webpackChunksyseye"]||[]).push([[950],{950:(e,a,t)=>{t.r(a),t.d(a,{default:()=>T});var l=t(3673);const r=(0,l.Uk)(" DuckSysEye内部测试版本v0.0.0.1 "),i=(0,l.Uk)(" 仪表盘 "),n=(0,l.Uk)(" 未处理威胁列表 "),o=(0,l.Uk)(" 已处理威胁列表 "),u=(0,l.Uk)(" 已忽略威胁列表 ");function c(e,a,t,c,s,d){const m=(0,l.up)("q-toolbar-title"),w=(0,l.up)("q-btn"),p=(0,l.up)("q-toolbar"),b=(0,l.up)("q-header"),_=(0,l.up)("q-icon"),h=(0,l.up)("q-item-section"),v=(0,l.up)("q-item"),f=(0,l.up)("q-list"),W=(0,l.up)("q-scroll-area"),k=(0,l.up)("q-drawer"),g=(0,l.up)("router-view"),y=(0,l.up)("q-page-container"),L=(0,l.up)("q-layout"),Z=(0,l.Q2)("ripple");return(0,l.wg)(),(0,l.j4)(L,{view:"lHh Lpr lFf",style:{"background-color":"rgb(239, 243, 246)"}},{default:(0,l.w5)((()=>[(0,l.Wm)(b,{elevated:"","height-hint":"98"},{default:(0,l.w5)((()=>[(0,l.Wm)(p,{class:"text-primary bg-white"},{default:(0,l.w5)((()=>[(0,l.Wm)(m,null,{default:(0,l.w5)((()=>[r])),_:1}),(0,l.Wm)(w,{flat:"",round:"",dense:"",icon:"more_vert"})])),_:1})])),_:1}),(0,l.Wm)(k,{"show-if-above":"",mini:e.miniState,onMouseover:a[4]||(a[4]=a=>e.miniState=!1),onMouseout:a[5]||(a[5]=a=>e.miniState=!0),width:200,breakpoint:500,bordered:"",class:"bg-white text-primary"},{default:(0,l.w5)((()=>[(0,l.Wm)(W,{class:"fit"},{default:(0,l.w5)((()=>[(0,l.Wm)(f,{padding:""},{default:(0,l.w5)((()=>[(0,l.wy)(((0,l.wg)(),(0,l.j4)(v,{active:"dashboard"==e.selectLabel,clickable:"","active-class":"menu-active",onClick:a[0]||(a[0]=a=>e.selectLabel="dashboard"),to:"/page/dashboard"},{default:(0,l.w5)((()=>[(0,l.Wm)(h,{avatar:""},{default:(0,l.w5)((()=>[(0,l.Wm)(_,{name:"dashboard"})])),_:1}),(0,l.Wm)(h,null,{default:(0,l.w5)((()=>[i])),_:1})])),_:1},8,["active"])),[[Z]]),(0,l.wy)(((0,l.wg)(),(0,l.j4)(v,{active:"non_hanlde_report"==e.selectLabel,clickable:"","active-class":"menu-active",onClick:a[1]||(a[1]=a=>{e.selectLabel="non_hanlde_report",e.routerToThreatList(0)})},{default:(0,l.w5)((()=>[(0,l.Wm)(h,{avatar:""},{default:(0,l.w5)((()=>[(0,l.Wm)(_,{name:"report"})])),_:1}),(0,l.Wm)(h,null,{default:(0,l.w5)((()=>[n])),_:1})])),_:1},8,["active"])),[[Z]]),(0,l.wy)(((0,l.wg)(),(0,l.j4)(v,{active:"handle_report"==e.selectLabel,clickable:"","active-class":"menu-active",onClick:a[2]||(a[2]=a=>{e.selectLabel="handle_report",e.routerToThreatList(1)})},{default:(0,l.w5)((()=>[(0,l.Wm)(h,{avatar:""},{default:(0,l.w5)((()=>[(0,l.Wm)(_,{name:"done"})])),_:1}),(0,l.Wm)(h,null,{default:(0,l.w5)((()=>[o])),_:1})])),_:1},8,["active"])),[[Z]]),(0,l.wy)(((0,l.wg)(),(0,l.j4)(v,{active:"ingore_report"==e.selectLabel,clickable:"","active-class":"menu-active",onClick:a[3]||(a[3]=a=>{e.selectLabel="ingore_report",e.routerToThreatList(2)})},{default:(0,l.w5)((()=>[(0,l.Wm)(h,{avatar:""},{default:(0,l.w5)((()=>[(0,l.Wm)(_,{name:"texture"})])),_:1}),(0,l.Wm)(h,null,{default:(0,l.w5)((()=>[u])),_:1})])),_:1},8,["active"])),[[Z]])])),_:1})])),_:1})])),_:1},8,["mini"]),(0,l.Wm)(y,null,{default:(0,l.w5)((()=>[(0,l.Wm)(g)])),_:1})])),_:1})}const s=(0,l.aZ)({name:"MainLayout",setup(){return{}},data:function(){return{selectLabel:"non_hanlde_report",drawer:!1,miniState:!0}},methods:{routerToThreatList(e){this.$router.push({name:"index",params:{queryIndex:e}})}}});var d=t(4260),m=t(9214),w=t(3812),p=t(9570),b=t(3747),_=t(9400),h=t(2901),v=t(7704),f=t(7011),W=t(3414),k=t(2035),g=t(4554),y=t(2652),L=t(6489),Z=t(7518),q=t.n(Z);const Q=(0,d.Z)(s,[["render",c]]),T=Q;q()(s,"components",{QLayout:m.Z,QHeader:w.Z,QToolbar:p.Z,QToolbarTitle:b.Z,QBtn:_.Z,QDrawer:h.Z,QScrollArea:v.Z,QList:f.Z,QItem:W.Z,QItemSection:k.Z,QIcon:g.Z,QPageContainer:y.Z}),q()(s,"directives",{Ripple:L.Z})}}]);

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,151 +1,217 @@
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
app = Flask(__name__,
template_folder="./templates",
static_folder="./templates",
static_url_path="")
app.jinja_env.variable_start_string = '{.<'
app.jinja_env.variable_end_string = '>.}'
import html
import rule
import statistics
app = Flask(
__name__,
template_folder="./templates",
static_folder="./templates",
static_url_path="",
)
app.jinja_env.variable_start_string = "{.<"
app.jinja_env.variable_end_string = ">.}"
@app.route('/')
@app.route("/")
def root():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
return render_template("index.html")
@app.route('/static/<path:path>')
@app.route("/static/<path:path>")
def on_vue_static(path):
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
return app.send_static_file("./" + path)
@app.route('/plugin/<path:path>')
@app.route("/plugin/<path:path>")
def on_plugin_access(path):
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
return plugin.dispath_html_draw(path)
@app.route('/api/v1/get/plugin_menu')
@app.route("/api/v1/get/plugin_menu")
def plugin_menu():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
return {'data': {'menu': plugin.dispath_html_menu()}}
return {"data": {"menu": plugin.dispath_html_menu()}}
@app.route('/api/v1/get/threat_statistics', methods=['GET'])
@app.route("/api/v1/get/threat_statistics", methods=["GET"])
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/get/process_chain/handle', methods=['GET'])
@app.route("/api/v1/query/white_list_all", methods=["GET"])
def white_list_query_all():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
all_list = sql.query_all_white_list()
result = []
for iter in all_list:
result.append({
"hash": iter[1],
"path": iter[2],
"timestamp": iter[3],
"reason": iter[4]
})
return {"status": "success", "result": result}
@app.route("/api/v1/query/white_list", methods=["GET"])
def white_list_query():
hash = request.args.get("hash")
if request.remote_addr not in config.ALLOW_ACCESS_IP or hash is None or len(hash) == 0:
return "Access Denied"
hash = hash.lower()
result = 0
if hash in hash_white_list.g_white_list:
result = 1
return {"status": "success", "result": result}
@app.route("/api/v1/del/white_list", methods=["GET"])
def white_list_del():
hash = request.args.get("hash")
if request.remote_addr not in config.ALLOW_ACCESS_IP or hash is None or len(hash) == 0:
return "Access Denied"
hash = hash.lower()
if hash in hash_white_list.g_white_list:
sql.delete_white_list(hash)
hash_white_list.g_white_list.remove(hash)
return {"status": "success"}
@app.route("/api/v1/set/white_list", methods=["POST"])
def white_list_set():
body_data = request.data.decode()
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
json_data = json.loads(body_data)
hash = html.escape(json_data["hash"]).lower()
path = html.escape(json_data["path"]).lower()
reason = html.escape(json_data["reason"])
hash_white_list.add_white_list(path, hash, reason)
return {"status": "success"}
@app.route("/api/v1/get/process_chain/handle", methods=["GET"])
def handle_chain_data():
id = request.args.get('id')
handletype = request.args.get('handletype')
if request.remote_addr not in config.ALLOW_ACCESS_IP or (id is None or handletype is None):
id = request.args.get("id")
handletype = request.args.get("handletype")
if request.remote_addr not in config.ALLOW_ACCESS_IP or (
id is None or handletype is None
):
return "Access Denied"
sql.handle_threat_log(id, handletype)
return {'data': {'success': 1}}
return {"data": {"success": 1}}
@app.route('/api/v1/get/process_chain/delete', methods=['GET'])
@app.route("/api/v1/get/process_chain/delete", methods=["GET"])
def delete_chain_data():
id = request.args.get('id')
id = request.args.get("id")
if request.remote_addr not in config.ALLOW_ACCESS_IP or id is None:
return "Access Denied"
sql.delete_threat(id)
return {'data': {'success': 1}}
return {"data": {"success": 1}}
@app.route('/api/v1/get/process_chain/pull', methods=['GET'])
@app.route("/api/v1/get/process_chain/pull", methods=["GET"])
def pull_chain_data():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
id = request.args.get('id')
id = request.args.get("id")
return_data = {}
if id is not None:
threat_data = sql.query_one_threat(id)
return_data = {
'host': threat_data[1],
'chain_hash': threat_data[2],
'type': threat_data[3],
'risk_score': threat_data[4],
'hit_rule': json.loads(threat_data[5]),
'chain': json.loads(threat_data[6]),
'is_end': threat_data[7]
"host": threat_data[1],
"chain_hash": threat_data[2],
"type": threat_data[3],
"risk_score": threat_data[4],
"hit_rule": json.loads(threat_data[5]),
"hit_attck": json.loads(threat_data[6]),
"chain": json.loads(threat_data[7]),
"is_end": threat_data[8],
}
return {'data': return_data}
return {"data": return_data}
@app.route('/api/v1/get/process_chain/all')
@app.route("/api/v1/get/process_chain/all")
def process_chain():
# -1全部 0未处理的 1处理的 2忽略的
query_type = request.args.get('query_type')
query_type = request.args.get("query_type")
if request.remote_addr not in config.ALLOW_ACCESS_IP or query_type is None:
return "Access Denied"
threat_datas = sql.query_all_threat_log(query_type)
return_data = []
for iter in threat_datas:
return_data.append({
'host': iter[0],
'chain_hash': iter[1],
'hit_rule': json.loads(iter[2]),
'time': iter[3],
'type': iter[4],
'risk_score': iter[5],
'id': iter[6],
'is_end': iter[7],
'start_process': json.loads(iter[8]),
})
return {'data': return_data}
return_data.append(
{
"host": iter[0],
"chain_hash": iter[1],
"hit_rule": json.loads(iter[2]),
"time": iter[3],
"type": iter[4],
"risk_score": iter[5],
"id": iter[6],
"is_end": iter[7],
"start_process": json.loads(iter[8]),
"attck_hit_list": json.loads(iter[10]),
}
)
return {"data": return_data}
@app.route('/api/v1/process', methods=['POST'])
@app.route("/api/v1/process", methods=["POST"])
def process():
if request.method == 'POST':
if request.method == "POST":
# print(request.data)
body_data = request.data.decode()
# 转小写
host = request.remote_addr
log.process_log(host, json.loads(body_data.lower()), body_data)
statistics.update_loged_num(host)
return {'status': 'success'}
return {"status": "success"}
if __name__ == '__main__':
@ app.route("/api/v1/log_hunt", methods=["POST"])
def log_rescan():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
start_time = request.args.get("start_time")
end_time = request.args.get("end_time")
raw_logs = sql.select_process_raw_log_by_time(
int(start_time), int(end_time))
threat_data = log.process_raw_log(raw_logs)
return {"data": threat_data}
if __name__ == "__main__":
plugin.reload_plugs()
sql.init()
rule.init_rule()
hash_white_list.synchronization_white_list()
# 如果你觉得日志太多了,去掉这个注释...
flask_log = logging.getLogger('werkzeug')
flask_log = logging.getLogger("werkzeug")
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,140 +1,105 @@
<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> DuckSysEye内部测试版本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>
<template v-for="(item, index) in plugin" v-bind:key="index">
<q-item
:active="selectLabel == item['name']"
clickable
v-ripple
active-class="menu-active"
@click="
selectLabel = item['name'];
routerToPlugin(item['html']);
"
>
<q-item-section avatar>
<q-icon :name="item['icon']" />
</q-item-section>
<q-item-section> {{ item["name"] }} </q-item-section>
</q-item>
</template>
</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 },
components: {
HtmlPanel
},
name: 'MainLayout',
setup () {
return {}
},
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({
name: 'whitelist'
})
},
routerToThreatList (index) {
this.isInPlugin = false
this.$router.push({ name: 'index', params: { queryIndex: index } })
this.$router.push({
name: 'index',
params: {
queryIndex: index
}
})
},
routerToPlugin (url) {
this.isInPlugin = true
@@ -153,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>
@@ -174,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,203 +1,198 @@
<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>
<div class="row">
<div class="col"></div>
<div class="col">
<div class="row q-gutter-md q-mb-sm q-pa-lg">
<q-timeline layout="dense" side="right" color="red">
<template
v-if="!server_threat.data || server_threat.data.length == 0"
>
<h4>暂无可用数据,下次刷新时间 {{last_refresh}}...</h4>
</template>
<template
v-for="(threat, index) in server_threat.data"
:key="index"
>
<q-timeline-entry :subtitle="'主机:' + threat.host" side="left">
<div>
<q-card
flat
bordered
style="overflow: auto"
:thumb-style="thumbStyle"
:bar-style="barStyle"
>
<q-card-section horizontal>
<div class="bg-red-5">&nbsp;</div>
<q-card-actions vertical class="justify-around q-px-md">
<div>进程链hash: {{ threat.chain_hash }}</div>
<div>进程: {{ threat.start_process.path }}</div>
<div>用户: {{ threat.start_process.user }}</div>
<div>
分数:
<q-chip
square
color="orange"
text-color="white"
icon-right="visibility"
>
{{ threat.risk_score }}
</q-chip>
</div>
<div>
活动状态:
<q-chip
square
:color="threat.is_end == 1 ? 'negative' : 'red'"
text-color="white"
>
{{ threat.is_end == 1 ? "已结束" : "进行中" }}
</q-chip>
</div>
<div>
产生的威胁:
<template
v-for="(index, operation) in threat.hit_rule"
:key="index"
>
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
</template>
</div>
<div>
<q-btn
flat
color="accent"
@click="show_details(threat.id)"
icon="open_in_new"
>
查看详情
</q-btn>
<q-btn
flat
color="accent"
@click="search_vt(threat.start_process.hash)"
icon="search"
>
在VT上搜索
</q-btn>
<q-btn
flat
color="accent"
@click="handle_threat(threat.id, 1)"
icon="done"
>
确认威胁
</q-btn>
<q-btn
flat
color="accent"
@click="handle_threat(threat.id, 2)"
icon="texture"
>
忽略威胁
</q-btn>
<q-btn
flat
color="accent"
icon="close"
@click="delete_threat(threat.id)"
>
删除报警
</q-btn>
</div>
</q-card-actions>
</q-card-section>
</q-card>
</div>
</q-timeline-entry>
</template>
</q-timeline>
<div class="col"></div>
<div class="col">
<div class="row q-gutter-md q-mb-sm q-pa-lg">
<q-timeline layout="dense" side="right" color="red">
<template v-if="!server_threat.data || server_threat.data.length == 0">
<h4>暂无可用数据,下次刷新时间 {{last_refresh}}...</h4>
</template>
<template v-for="(threat, index) in server_threat.data" :key="index">
<q-timeline-entry :subtitle="'主机:' + threat.host" side="left">
<div>
<q-card flat bordered style="overflow: auto" :thumb-style="thumbStyle" :bar-style="barStyle">
<q-card-section horizontal>
<div class="bg-red-5">&nbsp;</div>
<q-card-actions vertical class="justify-around q-px-md">
<div>进程链hash: {{ threat.chain_hash }}</div>
<div>进程: {{ threat.start_process.path }}</div>
<div>用户: {{ threat.start_process.user }}</div>
<div>
分数:
<q-chip square color="orange" text-color="white" icon-right="visibility">
{{ threat.risk_score }}
</q-chip>
</div>
<div>
活动状态:
<q-chip square :color="threat.is_end == 1 ? 'negative' : 'red'" text-color="white">
{{ threat.is_end == 1 ? "已结束" : "进行中" }}
</q-chip>
</div>
<div>
ATTCK命中:
<template v-for="(index, operation) in threat.attck_hit_list" :key="index">
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
</template>
</div>
<div>
产生的威胁:
<template v-for="(index, operation) in threat.hit_rule" :key="index">
<q-chip square color="red" text-color="white">
{{ operation }}&nbsp;({{ index }})
</q-chip>
</template>
<template v-if="JSON.stringify(threat.hit_rule) == '{}'">
<q-chip square color="negative" text-color="white">
<!--crowdstrike: 这活我熟-->
机器学习引擎
</q-chip>
</template>
</div>
<div>
<q-btn flat color="accent" @click="show_details(threat.id)" icon="open_in_new">
查看详情
</q-btn>
<q-btn flat color="accent" @click="search_vt(threat.start_process.hash)" icon="search">
在VT上搜索
</q-btn>
<q-btn flat color="accent" @click="handle_threat(threat.id, 1)" icon="done">
确认威胁
</q-btn>
<q-btn flat color="accent" @click="handle_threat(threat.id, 2)" icon="texture">
忽略威胁
</q-btn>
<q-btn flat color="accent" icon="close" @click="delete_threat(threat.id)">
删除报警
</q-btn>
</div>
</q-card-actions>
</q-card-section>
</q-card>
</div>
</q-timeline-entry>
</template>
</q-timeline>
</div>
</div>
</div>
<div class="col"></div>
<div class="col"></div>
</div>
</div>
<q-dialog
v-model="dialog"
persistent
:maximized="maximizedToggle"
transition-show="slide-up"
transition-hide="slide-down"
>
<q-card class="text-white">
<q-bar>
<q-space></q-space>
<q-btn
dense
flat
icon="minimize"
@click="maximizedToggle = false"
:disable="!maximizedToggle"
>
<q-tooltip
v-if="maximizedToggle"
content-class="bg-white text-primary"
>Minimize</q-tooltip
>
</q-btn>
<q-btn
dense
flat
icon="crop_square"
@click="maximizedToggle = true"
:disable="maximizedToggle"
>
<q-tooltip
v-if="!maximizedToggle"
content-class="bg-white text-primary"
>Maximize</q-tooltip
>
</q-btn>
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="row" style="width: 100%; height: 100%">
<div ref="main_draw" style="width: 100%; height: 100%; margin-left: 5%">
1
</div>
</div>
</div>
<q-dialog v-model="addwhiteListHash" persistent transition-show="scale" transition-hide="scale">
<q-card style="min-width: 350px">
<q-card-section>
<div class="text-h6">填写缘由</div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-input dense v-model="this.whiteListPostData.reason" autofocus />
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn flat label="取消" @click="addwhiteListHash = false" v-close-popup />
<q-btn flat label="加入白名单" v-close-popup @click="add_to_white_hash_post()" />
</q-card-actions>
</q-card>
</q-dialog>
</q-dialog>
<q-dialog v-model="dialog" persistent :maximized="maximizedToggle" transition-show="slide-up" transition-hide="slide-down">
<q-card class="text-white">
<q-bar>
<q-space></q-space>
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="row" style="width: 100%; height: 100%">
<div ref="main_draw" style="width: 100%; height: 100%; margin-left: 5%">
1
</div>
</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-section>活跃状态: {{processChainDetails.active ? "运行中" : "已结束"}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程名字: {{processChainDetails.name}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程路径: {{processChainDetails.path}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程参数: {{processChainDetails.params}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程id: {{processChainDetails.pid}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>父进程id: {{processChainDetails.ppid}}</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>进程hash: {{processChainDetails.md5}}</q-item-section>
</q-item>
<q-separator />
<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">
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
</template>
<template v-if="JSON.stringify(processChainDetails.hitRules) == '{}'">
<q-chip square color="rgb(239,243,246)">
</q-chip>
</template>
</q-item-section>
</q-item>
<q-item>
<q-item-section>attck矩阵:
<template v-for="(index, operation) in processChainDetails.hitAttck" :key="index">
<q-chip square color="rgb(239,243,246)">
{{ operation }}&nbsp;({{ index }})
</q-chip>
</template>
<template v-if="JSON.stringify(processChainDetails.hitAttck) == '{}'">
<q-chip square color="rgb(239,243,246)">
</q-chip>
</template>
</q-item-section>
</q-item>
<q-item>
<q-btn icon="search" outline style="color: grey;width: 100%;" label="搜索hash" @click="search_vt(processChainDetails.md5)" />
</q-item>
<q-item>
<template v-if="processChainDetails.isWhite == false">
<q-btn icon="texture" outline style="color: grey;width: 100%;" label="加入白名单" @click="add_to_white_hash_pre(processChainDetails.path,processChainDetails.md5)" />
</template>
<template v-else>
<q-btn icon="clear" outline style="color: grey;width: 100%;" label="从白名单中删除" @click="delete_white_hash(processChainDetails.md5)" />
</template>
</q-item>
</q-list>
</q-drawer>
</q-card>
</q-dialog>
</template>
<script>
import { defineComponent } from 'vue'
import {
defineComponent
} from 'vue'
import axios from 'axios'
import * as echarts from 'echarts'
@@ -205,7 +200,22 @@ export default defineComponent({
name: 'PageIndex',
data: function () {
return {
addwhiteListHash: false,
whiteListPostData: {
path: '',
hash: '',
reason: ''
},
processChainShowDetails: false,
last_refresh: 360,
processChainDetails: {
hash: '',
prams: '',
hitRules: [],
hitAttck: [],
isWhite: false,
whiteListReason: ''
},
thumbStyle: {
right: '4px',
borderRadius: '5px',
@@ -220,43 +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: {},
@@ -264,6 +237,32 @@ export default defineComponent({
}
},
methods: {
delete_white_hash (hash) {
axios.get('/api/v1/del/white_list?hash=' + hash).then(res => {
this.processChainDetails.isWhite = false
})
},
query_white_hash (hash) {
axios.get('/api/v1/query/white_list?hash=' + hash).then(res => {
this.processChainDetails.isWhite = res.data.result === 1
})
},
add_to_white_hash_pre (path, hash) {
this.whiteListPostData = {
path: path,
hash: hash,
reason: ''
}
this.addwhiteListHash = true
console.log('addwhiteListHash', this.addwhiteListHash)
},
add_to_white_hash_post () {
axios
.post('/api/v1/set/white_list', this.whiteListPostData)
.then((response) => {
this.processChainDetails.isWhite = true
})
},
set_chain_data (data) {
if (data.path) {
const str = data.path.split('\\')
@@ -285,66 +284,80 @@ export default defineComponent({
formatter: function (params) {
const contextData = params.data
let result =
'<div>参数: ' +
contextData.params +
'</div>' +
'<div> hash: ' +
contextData.md5 +
'</div><div>命名规则列表: '
'<div>参数: ' +
contextData.params +
'</div>' +
'<div> hash: ' +
contextData.md5 +
'</div><div>命名规则列表: '
if (contextData.operationlist.length === 0) {
result += '无'
}
for (const key in contextData.operationlist) {
result +=
' ' + key + '[' + contextData.operationlist[key] + ']' + ' '
' ' + key + '[' + contextData.operationlist[key] + ']' + ' '
}
result += '</div>'
return result
}
},
series: [
{
roam: true,
type: 'tree',
id: 0,
name: 'tree1',
data: [this.select_chain_data],
top: '5%',
left: '15%',
bottom: '22%',
right: '20%',
edgeShape: 'polyline',
edgeForkPosition: '63%',
initialTreeDepth: 60,
lineStyle: {
width: 2
},
series: [{
roam: true,
type: 'tree',
id: 0,
name: 'tree1',
data: [this.select_chain_data],
top: '5%',
left: '15%',
bottom: '22%',
right: '20%',
edgeShape: 'polyline',
edgeForkPosition: '63%',
initialTreeDepth: 60,
lineStyle: {
width: 2
},
label: {
backgroundColor: '#fff',
position: 'left',
verticalAlign: 'middle',
align: 'right'
},
leaves: {
label: {
backgroundColor: '#fff',
position: 'left',
position: 'right',
verticalAlign: 'middle',
align: 'right'
},
leaves: {
label: {
position: 'right',
verticalAlign: 'middle',
align: 'left'
}
},
emphasis: {
focus: 'descendant'
},
symbolSize: [40, 50], // 宽40 高50
symbol:
'image://data:image/png;base64,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',
expandAndCollapse: true,
animationDuration: 550,
animationDurationUpdate: 750
}
]
align: 'left'
}
},
emphasis: {
focus: 'descendant'
},
symbolSize: [30, 30], // 宽40 高50
symbol: 'image://data:image/png;base64,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',
expandAndCollapse: false,
animationDuration: 350,
animationDurationUpdate: 450
}]
}
myChart.setOption(option)
myChart.on('click', params => {
const data = params.data
this.processChainDetails = {
path: data.path,
active: data.active,
md5: data.md5,
name: data.name,
params: data.params,
pid: data.pid,
ppid: data.ppid,
hitRules: data.operationlist === undefined ? {} : data.operationlist,
hitAttck: data.attck_hit_list === undefined ? {} : data.attck_hit_list,
isWhite: false
}
this.query_white_hash(data.md5)
this.processChainShowDetails = true
})
},
search_vt (hash) {
window.open('https://www.virustotal.com/gui/search/' + hash, '_blank')
@@ -384,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
@@ -415,7 +412,6 @@ export default defineComponent({
data: []
}
this.server_threat.data = data.data
this.get_threatStatistics()
}
})
}

View File

@@ -0,0 +1,132 @@
<template>
<q-table class="q-pa-lg" :dense="$q.screen.lt.md" title="白名单列表" :columns="data_columns" :rows="data_columns_data" :loading="loading" v-model:pagination="pagination" @request="onRequest">
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="path" :props="props">{{ props.row.path }}</q-td>
<q-td key="hash" :props="props">{{ props.row.hash }}</q-td>
<q-td key="reason" :props="props">{{ props.row.reason }}</q-td>
<q-td key="timestamp" :props="props">{{ time_parase(props.row.timestamp) }}</q-td>
<q-td key="action" :props="props">
<q-btn color="red" label="移除白名单" @click="delete_white_hash(props.row.hash)" />
</q-td>
</q-tr>
</template>
</q-table>
</template>
<script>
import {
defineComponent
} from 'vue'
import axios from 'axios'
export default defineComponent({
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: false,
pagination: {
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10
}
}
},
mounted () {
this.onRequest({
pagination: this.pagination,
filter: undefined
})
},
methods: {
delete_white_hash (hash) {
axios.get('/api/v1/del/white_list?hash=' + hash).then(res => {
this.onRequest({
pagination: this.pagination,
filter: undefined
})
})
},
time_parase (pTime) {
// shijianchuo是整数否则要parseInt转换
const add0 = m => {
return m < 10 ? '0' + m : m
}
const time = new Date(Number(pTime))
console.log('time', pTime)
const y = time.getFullYear()
const m = time.getMonth() + 1
const d = time.getDate()
const h = time.getHours()
const mm = time.getMinutes()
const s = time.getSeconds()
return (
y +
'-' +
add0(m) +
'-' +
add0(d) +
' ' +
add0(h) +
':' +
add0(mm) +
':' +
add0(s)
)
},
onRequest (props) {
this.data_columns_data = []
this.loading = true
const {
page
} = props.pagination
axios.get('/api/v1/query/white_list_all').then(response => {
const data = response.data.result
console.log(data)
for (let index = 0; index < data.length; index++) {
const element = data[index]
this.data_columns_data.push(element)
}
this.pagination.page = page
this.pagination.rowsNumber = this.data_columns_data.length
this.pagination.rowsPerPage = this.data_columns_data.length
this.loading = false
})
}
}
})
</script>

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') }
]
},
{
@@ -12,7 +12,8 @@ const routes = [
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: 'dashboard', component: () => import('pages/Dashboard.vue') },
{ path: 'index', name: 'index', component: () => import('pages/Index.vue') }
{ path: 'index', name: 'index', component: () => import('pages/Index.vue') },
{ path: 'index', name: 'whitelist', component: () => import('pages/Whitelist.vue') }
]
},
// Always leave this as last one,

106
doc_day0_rule.md Normal file
View File

@@ -0,0 +1,106 @@
### 规则编写教程
首先明确一点,rmeye有绝大部分威胁的检出能力,但是受限于规则,这些能力相当于只记录但是没生效.因此编写规则是一件重要的事情.在一切的开始,请您阅读ATT&CK的思想:
https://key08.com/index.php/2022/08/09/1505.html
本文以检测`mimikatz的关键dll加载`为例
`mimikatz`会加载: `C:\\Windows\\System32\\samlib.dll` 本文以他为例
### sysmon客户端规则
在编写服务端规则之前,请确保`sysmon`有相关配置.因为`rmeye`依赖于`sysmon`,得有客户端规则,才有服务端规则:
打开客户端安装目录下的`sysmon.xml`:
![9](Image/9.png)
我们需要检测`dll`加载,所以请把目光看到`ImageLoad`
其他的`sysmon`字段请自行谷歌或者百度了解.本项目有个`provider.json`那个是`sysmon`的所有字段.可以参考,打开`provider.json` ,找到`ImageLoad`
![10](Image/10.png)
可以看到`sysmon`记录了非常多有用的字段,对于我们来说,我们只需要`ImageLoaded`
`sysmon.xml`找到`Imageload`这个block,
```c
<RuleGroup name="" groupRelation="or">
<ImageLoad onmatch="include">
<!--NOTE: Using "include" with no rules means nothing in this section will be logged-->
</ImageLoad>
</RuleGroup>
```
我们给他增加一个,接受加载`C:\\Windows\\System32\\samlib.dll`的日志:
```c
<RuleGroup name="" groupRelation="or">
<ImageLoad onmatch="include">
<ImageLoaded condition="is">C:\Windows\System32\samlib.dll</ImageLoaded>
</ImageLoad>
</RuleGroup>
```
保存,更新`sysmon`的配置文件,在`rmeye`的安装目录或者在`sysmon`的安装目录执行:
```
sysmon -c sysmon.xml
```
![11](Image/11.png)
看到这个,说明你成功了,其他的说明规则有错误,检查一下你写的规则.
### 服务端规则编写
客户端有了搜集日志能力后,是时候编写服务端的了:
打开服务端目录下的`server/rules/action.py`
在末尾添加:
```
{
'rules': [
'action == "imageload" and imageloaded == "c:\\windows\\system32\\samlib.dll"',
],
'score': 50,
'name': 'samlib的dll被加载'
}
```
其中
```
rules: 规则的列表,可以是多个规则,是and关系
score: 是分数,跟config.py里面的报警分数有关系
name: 规则名字
规则全部小写,==代表正常匹配,如果是=~代表使用python正则
```
保存,重启服务端,如果不出意外您应该看得到最新的刚刚增加的规则:
![](Image/12.png)
**完整的 `服务端规则指南`**
[doc_server_rule_manual.md](./doc_server_rule_manual.md)
### 测试
找个mimikatz运行一下看看:
![](Image/13.png)
(为了测试,将分数设置高一点准没错)
至此,您就具备的检测mimikatz的一些行为的能力,当然这行为是不全的而且容易产生很多误报的,很多时候为了减少误报或者实现更精准的检测,您需要高级检出能力,这将在下一章插件检测中介绍.
下一章:
https://github.com/RoomaSec/RmEye/blob/main/doc_day1_plugin.md

148
doc_day1_plugin.md Normal file
View File

@@ -0,0 +1,148 @@
### 编写插件用于检测需要复杂上下文的威胁
在本章开始前,请先阅读:
https://github.com/RoomaSec/RmEye/blob/main/doc_day0_rule.md
rmeye提供了一个简陋的插件接口,用于检测需要上下文帮助的威胁.本文以检测mimikatz为例,编写一个插件:
mimikatz一定会加载如下dll:
```C
C:\Windows\System32\advapi32.dll
C:\Windows\System32\crypt32.dll
C:\Windows\System32\cryptdll.dll
C:\Windows\System32\gdi32.dll
C:\Windows\System32\imm32.dll
C:\Windows\System32\kernel32.dll
C:\Windows\System32\KernelBase.dll
C:\Windows\System32\msasn1.dll
C:\Windows\System32\msvcrt.dll
C:\Windows\System32\ntdll.dll
C:\Windows\System32\rpcrt4.dll
C:\Windows\System32\rsaenh.dll
C:\Windows\System32\samlib.dll
C:\Windows\System32\sechost.dll
C:\Windows\System32\secur32.dll
C:\Windows\System32\shell32.dll
C:\Windows\System32\shlwapi.dll
C:\Windows\System32\sspicli.dll
C:\Windows\System32\user32.dll
C:\Windows\System32\vaultcli.dll
```
当有这些的DLL在一个程序被加载的时候,我们就要注意了.但是我们之前的规则是单条的,没有上下文,因此需要通过插件系统实现,本文默认你已经给sysmon增加了以上的datasoruce
### 插件编写
在服务端`plugins`目录下新建文件夹`mimikazt_detect`然后新建一个文件`mimikatz_detect.py`,如下是模板:
```python
import global_vars
import process
rm_plugs_config = {
"enable": True, #是否启用插件
"author": "huoji",
"description": "检测mimikatz",
"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('mimikatz检测插件 2022/9/5 by huoji')
```
为了检测,我们需要记录每一个dll加载的行为并且保存到进程上下文中,具体看代码
```python
import global_vars
import process
rm_plugs_config = {
"enable": True,
"author": "huoji",
"description": "检测mimikatz",
"version": "0.0.1"
}
mimikatz_dll_list = [
'c:\\windows\\system32\\advapi32.dll',
'c:\\windows\\system32\\crypt32.dll',
'c:\\windows\\system32\\cryptdll.dll',
'c:\\windows\\system32\\gdi32.dll',
'c:\\windows\\system32\\imm32.dll',
'c:\\windows\\system32\\kernel32.dll',
'c:\\windows\\system32\\kernelbase.dll',
'c:\\windows\\system32\\msasn1.dll',
'c:\\windows\\system32\\msvcrt.dll',
'c:\\windows\\system32\\ntdll.dll',
'c:\\windows\\system32\\rpcrt4.dll',
'c:\\windows\\system32\\rsaenh.dll',
'c:\\windows\\system32\\samlib.dll',
'c:\\windows\\system32\\sechost.dll',
'c:\\windows\\system32\\secur32.dll',
'c:\\windows\\system32\\shell32.dll',
'c:\\windows\\system32\\shlwapi.dll',
'c:\\windows\\system32\\sspicli.dll',
'c:\\windows\\system32\\user32.dll',
'c:\\windows\\system32\\vaultcli.dll',
]
def rule_new_process_create(current_process: process.Process, host, raw_log_data, json_log_data):
# 服务端提供了一个 plugin_var 变量用于存放当前进程插件的上下文
current_process.plugin_var['mimikatz_matched_num'] = 0
current_process.plugin_var['mimikatz_detected'] = False
return global_vars.THREAT_TYPE_NONE
def rule_new_process_action(current_process: process.Process, host, raw_log_data, json_log_data):
global mimikatz_dll_list
# 如果日志的action是imageload(dll加载)
if json_log_data['action'] == 'imageload' and current_process.plugin_var['mimikatz_detected'] == False:
# 把日志中的dll路径取出来
dll_path = json_log_data['data']['imageloaded']
# 如果dll的路径在mimikatz的路径里面,进程上下文+1
if dll_path in mimikatz_dll_list:
current_process.plugin_var['mimikatz_matched_num'] += 1
if current_process.plugin_var['mimikatz_matched_num'] >= len(mimikatz_dll_list):
current_process.set_score(300, "[mimikatz]检测到疑似mimikatz进程")
current_process.plugin_var['mimikatz_detected'] = True
return global_vars.THREAT_TYPE_PROCESS
return global_vars.THREAT_TYPE_NONE
def rule_init():
pass
def plugin_init():
print('mimikatz检测插件 2022/9/5 by huoji')
```
### 测试
运行mimikatz:
![](Image/14.png)
当然还会有其他的情况的误报!这需要你完善插件.
如果遇到不懂的地方,可以提issue.欢迎提问

156
doc_server_rule_manual.md Normal file
View File

@@ -0,0 +1,156 @@
# 服务端规则指南
编写服务端规则前,请您详细阅读本文,以便了解规则背后的故事,并帮助您更好的编写 RmEye 规则。
# 规则在何时被应用
RmEye 通过本地部署的客户端,向服务器传输行为事件。 \
服务器在收集并解析了行为事件日志后,会立即调用规则匹配函数检测该行为是否被某条规则命中。
# 规则分类
基于 RmEye 的设计思想,一切动作都基于其进程载体,所以,规则被分为 动作action和 进程process两种类型。
- [`进程规则`]
用于在进程启动事件日志中,检测新进程的启动上下文,判断其是否为可疑行为。
- [`动作规则`]
用于在非进程启动事件日志中,检测其特定行为上下文,判断其是否为可疑行为。
RmEye 动作规则列表编写于 `/Server/rules/py/action.py` 文件中;进程规则列表编写于 `/Server/rules/py/process.py` 文件中。
# 规则单元数据结构
```json
{
'rules': [
'originalfilename =~ ".*wbadmin.exe.*" and commandline =~ ".*delete.*"',
],
'score': 70,
'name': 'wbadmin'
}
```
这是一个 进程process类型的示例规则单元它是一个 dict 数据,包含有三个 item\
分别是:`rules`, `score`, `name`
- [`rules`]-> `list`:
其中包含一个或多个使用 `rule_engine` 语法的规则匹配表达式,每个表达式间的关系为 `或`,即任何一个表达式被匹配,都认为该规则已命中。
- [`score`]-> `int`:
由一个整数表示的规则匹配分值
- [`name`]-> `str`:
规则名称
# 适用于 RmEye 的 `rule_engine` 规则匹配表达式
`rule_engine` 表达式是服务端规则的核心,它允许用户定义一个 key-value 类型的 Query 表达式,以匹配一个 dict 数据;\
表达式的左值匹配 dict 数据中的特定键名key\
右值允许适用通配符、数字、字符串等进行完全匹配或模糊匹配 dict 数据中对应左值键名的值value。\
需要特别注意的是,必须定义 RmEye 数据源事件日志中存在的左值,才可以使规则完全按照预期工作。
# 进程规则已支持的通用左值定义
- `processid`
进程 PID
- `image`
进程文件路径
- `originalfilename`
进程原始文件名
- `hashes`
进程 MD5 哈希
- `commandline`
进程命令行
- `user`
进程用户名
- `integritylevel`
进程权限等级
- `parentprocessid`
父进程 PID
- `parentimage`
父进程文件路径
- `parentcommandline`
父进程命令行
- `parentuser`
父进程用户
# 动作规则已支持的特有左值定义
- `action`
动作类型,包括:
| action | 描述 |
| ---- | ---- |
| processaccess | 进程句柄访问 |
| pipecreate | 命名管道创建 |
| createremotethread | 远程线程创建 |
| filecreatestreamhash | 文件流创建 |
| registryadd | 注册表项新建 |
| registryvalueSet | 注册表值项设置 |
| registryobjectSet | 注册表对象设置 |
| dnsquery | DNS 查询 |
| networkconnect | 网络连接建立 |
| clipboardchange | 剪贴板访问 |
| processtampering | 进程执行流劫持 |
| filedeletedetected | 可执行文件删除 |
| filecreate | 文件创建 |
| imageload | DLL 加载 |
| processcreate | 进程创建(已分离为进程规则)|
| processterminal | 进程退出(内部保留)|
- `sourceimage` - 仅适用于动作 `processaccess`
源进程文件路径
- `targetimage` - 仅适用于动作 `processaccess`
目标进程文件路径
- `grantedaccess` - 仅适用于动作 `processaccess`
访问权限
- `calltrace` - 仅适用于动作 `processaccess`
调用栈Call Stack
- `pipename` - 仅适用于动作 `pipecreate`
管道名称
- `targetfilename` - 仅适用于动作 `filecreate`
目标文件名
- `imageloaded` - 仅适用于动作 `imageload`
已加载的映像名

View File

@@ -1,4 +1,4 @@
![image](Image/logo.png)
![image](Image/wx.png)
# RmEye
RmEye是一个window上的基于att&ck现代EDR设计思想的威胁响应工具.
@@ -19,12 +19,57 @@ RmEye是一个window上的基于att&ck现代EDR设计思想的威胁响应工具
5. 对RPC、COM、ALPC基本无能为力
6. 不支持更高级的扩展检测,如检测脚本、下发规则,主机链
7. 受限于Sysmon,很多att&ck的T没有覆盖,也无法覆盖.
8. 没有响应能力,只能被动记录.
请牢记,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`的检测
2022/9/20:
秋季重大更新,规则部分完全重构,目前检出完全基于attck的software.文档有空了再更新
2022/9/8:
增加服务端规则指南: \
[doc_server_rule_manual.md](./doc_server_rule_manual.md)
2022/9/5:
增加规则编写教程:
https://github.com/RoomaSec/RmEye/blob/main/doc_day0_rule.md
增加`mimikatz`检测
2022/8/31:
增加进程白名单系统,现在能给进程加白名单了.在打开进程链后,点击某个进程加入白名单即可
2022/8/29:
增加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)
powershell:
仪表盘(2022/9/22更新):
![image](Image/dashboard.png)
进程链行为回溯
![image](Image/8.png)
powershell恶意执行:
![image](Image/2.png)
apt样本:
![image](Image/3.png)
@@ -34,7 +79,12 @@ apt样本:
![image](Image/5.png)
offic宏钓鱼:
![image](Image/6.png)
uac提权检测:
![image](Image/7.png)
mimikatz检测:
![image](Image/14.png)
brc4检测:
![image](Image/15.png)
### 待做列表
1. 更好的前端(目前是VUE-CDN模式,不太好,想换成VUE-CLI) 已经完成
2. 日志回放【目前重点】
@@ -47,7 +97,7 @@ offic宏钓鱼:
9. 完善目前的插件系统【目前重点】
10. 云日志检测能力【目前重点】
### 安装
下载release( xxxxxxxx ),里面有客户端,服务端自行clone本项目
下载release( https://github.com/RoomaSec/RmEye/releases ),里面有客户端,服务端自行clone本项目
服务端是python3编写,安装完依赖库后输入
```
python webserver.py
@@ -103,12 +153,15 @@ 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的规则支持是以插件的形式支持
3. 目前的规则字段完全依赖sysmon的字段,sysmon的字段请检查根目录下的provider.json(但是请记住纯小写,自行做大小写转换)
规则目前有两种规则:
规则目前在`Server/rules`目录规则目前有两种规则:
rule_engine:
如检测由CMD启动的ipconfig:
```
@@ -122,8 +175,11 @@ rule_engine:
```
分数代表的是本次规则给进程链所增加的分数,报警是根据前面的MAX_THREAT_SCORE设置的
具体编写方法请移步:
https://github.com/zeroSteiner/rule-engine
规则编写教程请移步:
https://github.com/RoomaSec/RmEye/blob/main/doc_day0_rule.md
规则引擎的语法请移步:
https://github.com/zeroSteiner/rule-engine
yara,需要安装插件,具体请看交流部分
@@ -138,12 +194,9 @@ https://github.com/VirusTotal/yara
https://github.com/SwiftOnSecurity/sysmon-config
请遵守相关库的开源协议.相关法律风险本项目不负任何责任
### 交流
开源的目的不是为了免费填鸭式教学,或者被免费拿去发公众号引流、去拿去集成产品方案去赚钱,而是要一起完善这个工具,从而实现共赢.
扫一扫加入这个工具内部测试群,这样就能获取实时动态
![image](Image/group.png)
### 特别感谢
@Pwn0x01 yara插件
@zeroSteiner 规则引擎插件
@SwiftOnSecurity 客户端规则
@SwiftOnSecurity 客户端规则
@Fplyth0ner-Combie 规则相关文档

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 ] -->
@@ -353,6 +353,8 @@
<Image condition="image">nmap.exe</Image>
<Image condition="image">psinfo.exe</Image>
<!--Ports: Suspicious-->
<DestinationPort name="HTTP" condition="is">80</DestinationPort> <!--SSH protocol, monitor admin connections-->
<DestinationPort name="HTTPS" condition="is">443</DestinationPort> <!--SSH protocol, monitor admin connections-->
<DestinationPort name="SSH" condition="is">22</DestinationPort> <!--SSH protocol, monitor admin connections-->
<DestinationPort name="Telnet" condition="is">23</DestinationPort> <!--Telnet protocol, monitor admin connections, insecure-->
<DestinationPort name="SMTP" condition="is">25</DestinationPort> <!--SMTP mail protocol port, insecure, used by threats-->
@@ -374,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-->
@@ -421,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>