This commit is contained in:
huoji
2022-08-22 20:14:03 +08:00
parent 7a2dad9291
commit 3475c90afe
50 changed files with 5523 additions and 19 deletions

155
.gitignore vendored Normal file
View File

@@ -0,0 +1,155 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
*.db
*.zip

BIN
Image/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

BIN
Image/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
Image/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
Image/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
Image/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
Image/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
Image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

214
LICENSE
View File

@@ -1,21 +1,201 @@
MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2022 RoomaSec
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

151
README.md
View File

@@ -1,2 +1,149 @@
# RmEye
戎马之眼是一个window上的基于att&ck模型的威胁监控工具.有效检测常见的未知威胁与已知威胁.防守方的利剑
![image](Image/logo.png)
# DuckSysEye
SysEye是一个window上的基于att&ck现代EDR设计思想的威胁响应工具.
不同于EDR,它轻量、高效.自身定位是轻量级威胁检出工具.
而不是繁重的、需要付费的、效果不明的所谓的EDR
### 功能特点
1. 基于att&ck设计.所有设计只是为了符合att&ck的攻击路径、攻击链(虽然规则里面没有标注T因为懒惰)
2. 轻量、高效.为了不适用繁重超占内存的ELK设计思路,而且要保证检出的同时保证不会太重,agent端使用了大量规则过滤,这样才使得后端使用sqlite作为数据库成为可能.单机日志平均一天4M.此外轻量级别的客户端一天只占40-400KB的内存.
3. 行为检出,让免杀成为过去式.基于att&ck设计,只看行为不看文件.文件类免杀已经成为过去式.
4. 高扩展性.可随需求定制功能
### SysEye 之所以不是 Edr/Xdr/Mdr/Ndr/XXXXXdr
1. SysEye没有流量监控
2. SysEye仅覆盖20%左右的datasource
3. SysEye没有联动WAF、IPS/IDS
4. SysEye没有实时拦截功能
5. 对RPC、COM、ALPC基本无能为力
6. 不支持更高级的扩展检测,如检测脚本、下发规则,主机链
7. 受限于Sysmon,很多att&ck的T没有覆盖,也无法覆盖.
请牢记,SysEye自身定位是轻量级威胁检出工具
### 检出截图
威胁列表:
![image](Image/1.png)
powershell:
![image](Image/2.png)
apt样本:
![image](Image/3.png)
勒索软件:
![image](Image/4.png)
网站入侵提权到执行cobalt strike:
![image](Image/5.png)
offic宏钓鱼:
![image](Image/6.png)
### 待做列表
1. 更好的前端(目前是VUE-CDN模式,不太好,想换成VUE-CLI) 已经完成
2. 日志回放【目前重点】
3. 威胁狩猎【目前重点】
4. att&ck热力图
5. 在线规则编辑器
6. 内网横向检测
7. iis、apache、nginx日志搜集分析(aka: XDR的实现)
8. 集成反病毒引擎
9. 完善目前的插件系统【目前重点】
10. 云日志检测能力【目前重点】
### 安装
下载release( xxxxxxxx ),里面有客户端,服务端自行clone本项目
服务端是python3编写,安装完依赖库后输入
```
python webserver.py
```
即可部署
服务端部署后,修改config.py里面的
```
# 检出阈值,越高越难检出但是也会越准确
MAX_THREAT_SCORE = 170
# 授权访问主站的IP列表.如果不在后台里面则不能访问后台
ALLOW_ACCESS_IP = ['127.0.0.1']
```
MAX_THREAT_SCORE代表报警分数,意思为进程链总分超过此分数则报警,越高越准但是也会漏报
ALLOW_ACCESS_IP代表允许的IP,只有在此名单里面的IP才能访问后台.请增加自己的IP地址
客户端则编辑config.ini
```
[communication]
server = http://192.168.111.189:5000
```
其中server改成你的服务端的地址
然后分发三个文件给客户端并且放在同一目录:
config.ini、install.cmd、SysEye.exe、sysmon.xml、Sysmon64.exe
之后管理员身份运行install.cmd安装sysmon与syseye
访问 http://服务器ip:5000(flask默认端口) 查看后台
当然一开始啥数据也没有,为了确认是否安装成功可以将webserver.py中的
```
flask_log = logging.getLogger('werkzeug')
flask_log.setLevel(logging.ERROR)
```
注释掉,检查有没有客户端的请求即可
手动安装(cmd脚本其实执行了这些命令):
```
//安装sysmon:
sysmon -i
//sysmon加载配置项
sysmon -c sysmon.xml
//安装syseye
syseye /install
```
### 卸载
卸载syseye:
在syseye目录下执行
```
SysEye /uninstall
```
如果您需要卸载sysmon
执行
```
sysmon /uninstall
```
即可干净卫生的卸载掉Syseye
### 规则相关的问题
1. 规则目前仅120条,很多攻击面没有覆盖,其他规则请访问《社区》
2. 规则目前只支持rule_engine与yara的规则,其中yara的规则支持是以插件的形式支持
3. 目前的规则字段完全依赖sysmon的字段,sysmon的字段请检查根目录下的provider.json(但是请记住纯小写,自行做大小写转换)
规则目前有两种规则:
rule_engine:
如检测由CMD启动的ipconfig:
```
{
'rules': [
'originalfilename =~ ".*cmd.exe" and commandline =~ ".*ipconfig.*"',
],
'score': 80,
'name': 'cmd启动ipconfig'
},
```
分数代表的是本次规则给进程链所增加的分数,报警是根据前面的MAX_THREAT_SCORE设置的
具体编写方法请移步:
https://github.com/zeroSteiner/rule-engine
yara,需要安装插件,具体请看交流部分
### 第三方引用库
1. sysmon
https://docs.microsoft.com/zh-cn/sysinternals/downloads/sysmon
2. rule_engine
https://github.com/zeroSteiner/rule-engine
3. yara
https://github.com/VirusTotal/yara
4. sysmon-config(客户端使用的默认的规则,但是我做了一些修改)
https://github.com/SwiftOnSecurity/sysmon-config
请遵守相关库的开源协议.相关法律风险本项目不负任何责任
### 交流
开源的目的不是为了免费填鸭式教学,或者被免费拿去发公众号引流、去拿去集成产品方案去赚钱,而是要一起完善这个工具,从而实现共赢.
目前我们有一个社区,供大家交流.
社区地址:http://xxxxxxxxxx
### 特别感谢
@Pwn0x01 yara插件
@zeroSteiner 规则引擎插件
@SwiftOnSecurity 客户端规则

4
Server/config.py Normal file
View File

@@ -0,0 +1,4 @@
# 检出阈值,越高越难检出但是也会越准确
MAX_THREAT_SCORE = 170
# 授权访问主站的IP列表.如果不在后台里面则不能访问后台
ALLOW_ACCESS_IP = ['127.0.0.1']

11
Server/global_vars.py Normal file
View File

@@ -0,0 +1,11 @@
import os
THREAT_TYPE_NONE = -1
THREAT_TYPE_PROCESS = 0
THREAT_TYPE_ROOTKIT = 1
THREAT_TYPE_LM = 2
THREAT_TYPE_HOSTSTATUS = 3
THREAT_TYPE_NETWORK = 4
PLUGS_PATH = os.path.dirname(os.path.realpath(__file__)) + "\\plugins\\"
g_plugs = []

195
Server/log.py Normal file
View File

@@ -0,0 +1,195 @@
import json
import time
import process
import rule
import sql
import global_vars
import config
import plugin
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 = ""
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)
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
if current_process is not None:
if current_process.chain.risk_score >= config.MAX_THREAT_SCORE:
if had_threat == global_vars.THREAT_TYPE_PROCESS:
current_process.chain.update_process_tree()
threat = sql.select_threat_by_chain_id(
host, current_process.chain.hash, global_vars.THREAT_TYPE_PROCESS
)
if len(threat) == 0:
process_info: process.Process = None
if len(current_process.chain.process_list) > 1:
process_info = current_process.chain.process_list[1]
else:
process_info = current_process
info_save_data = {
"path": process_info.path,
"hash": process_info.md5,
"params": process_info.params,
"user": process_info.user,
"create_time": process_info.time,
}
sql.push_threat_log(
host,
current_process.chain.risk_score,
json.dumps(current_process.chain.operationlist),
current_process.chain.hash,
current_process.chain.get_json(),
global_vars.THREAT_TYPE_PROCESS,
json.dumps(info_save_data),
)
else:
sql.update_threat_log(
host,
current_process.chain.risk_score,
json.dumps(current_process.chain.operationlist),
current_process.chain.hash,
current_process.chain.get_json(),
global_vars.THREAT_TYPE_PROCESS,
current_process.chain.active == False,
)
parent_pid = 0
target_pid = 0
self_hash = ""
target_image_path = ""
target_hash = ""
raw_json_log = json.loads(raw_log)
if current_process is not None:
chain_hash = current_process.chain.hash
parent_pid = current_process.ppid
if "TargetProcessId" in raw_json_log:
target_process: process.Process = current_process.chain.find_process_by_pid(
raw_json_log["TargetProcessId"]
)
target_pid = target_process.pid
target_image_path = target_process.path
target_hash = target_process.md5
self_hash = current_process.md5
sql.push_process_raw(
host,
raw_json_log,
rule_hit_name,
score,
chain_hash,
had_threat,
parent_pid,
target_pid,
self_hash,
target_image_path,
target_hash,
params,
user,
)
"""
for iter in process.g_ProcessChainList:
item: process.Process = iter
if item.risk_score >= config.MAX_THREAT_SCORE:
item.print_process()
"""

111
Server/plugin.py Normal file
View File

@@ -0,0 +1,111 @@
from imp import find_module, load_module
import global_vars
import sys
import os
def reload_plugs():
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
del sys.modules[plug_obj.__name__]
del plug_obj
global_vars.g_plugs = walk_path_get_plugs(
global_vars.PLUGS_PATH)
def walk_path_get_plugs(pPath):
plugs = []
for root, dirs, files in os.walk(pPath):
for file in files:
if file.endswith(".py"):
module_name = file[:-3]
plugs_path = os.path.join(root, file)
if module_name not in sys.modules:
file_handle, file_path, dect = find_module(
module_name, [root])
try:
print("load module:", module_name)
module_obj = load_module(
module_name, file_handle, file_path, dect)
print("load module_obj:", module_obj)
if hasattr(module_obj, "rm_plugs_config") == False \
or hasattr(module_obj, "plugin_init") == False \
or 'author' not in module_obj.rm_plugs_config.keys() \
or 'description' not in module_obj.rm_plugs_config.keys() \
or 'version' not in module_obj.rm_plugs_config.keys() \
or "enable" in module_obj.rm_plugs_config.keys() and module_obj.rm_plugs_config['enable'] == False:
del module_obj
del sys.modules[module_name]
continue
print('----------------------------------')
print('加载模块: {} 模块作者: {} 模块介绍: {} 版本: {}'.format(
module_name, module_obj.rm_plugs_config['author'], module_obj.rm_plugs_config['description'], module_obj.rm_plugs_config['version']))
plugs.append((plugs_path, module_obj))
module_obj.plugin_init()
finally:
if file_handle:
file_handle.close()
return plugs
def dispath_process_terminal(host, current_process, raw_log_data, json_log_data):
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "process_terminal"):
plug_obj.process_terminal(
current_process, host, raw_log_data, json_log_data)
def dispath_rule_new_process_create(host, current_process, raw_log_data, json_log_data):
threat_type = global_vars.THREAT_TYPE_NONE
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "rule_new_process_create"):
if threat_type == global_vars.THREAT_TYPE_NONE:
threat_type = plug_obj.rule_new_process_create(
current_process, host, raw_log_data, json_log_data)
else:
plug_obj.rule_new_process_create(
current_process, host, raw_log_data, json_log_data)
return threat_type
def dispath_rule_new_process_action(host, current_process, raw_log_data, json_log_data):
threat_type = global_vars.THREAT_TYPE_NONE
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "rule_new_process_action"):
if threat_type == global_vars.THREAT_TYPE_NONE:
threat_type = plug_obj.rule_new_process_action(
current_process, host, raw_log_data, json_log_data)
else:
plug_obj.rule_new_process_action(
current_process, host, raw_log_data, json_log_data)
return threat_type
def dispath_rule_init():
for index in range(len(global_vars.g_plugs)):
_, plug_obj = global_vars.g_plugs[index]
if hasattr(plug_obj, "rule_init"):
plug_obj.rule_init()
# 有性能问题,以后再说
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())
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()
return 'Access Denied '

View File

@@ -0,0 +1,43 @@
import global_vars
import yara
import glob
from pathlib import Path
rm_plugs_config = {
"enable": False,
"author": "huoji",
"description": "hello world插件示例",
"version": "0.0.1",
"html": "helloworld"
}
def html_menu():
# https://fonts.google.com/icons?selected=Material+Icons
return {'name': "示例插件", 'icon': 'lightbulb', 'html': rm_plugs_config['html']}
def html_draw():
return '<div>hello world</div>'
def process_terminal(current_process, host, raw_log_data, json_log_data):
print('[helloworld plugin] rule new process create')
def rule_new_process_create(current_process, host, raw_log_data, json_log_data):
print('[helloworld plugin] rule new process create')
return global_vars.THREAT_TYPE_NONE
def rule_new_process_action(current_process, host, raw_log_data, json_log_data):
print('[helloworld plugin] rule new process action')
return global_vars.THREAT_TYPE_NONE
def rule_init():
print('[helloworld plugin] rule init')
def plugin_init():
print('[helloworld plugin] plugin init')

313
Server/process.py Normal file
View File

@@ -0,0 +1,313 @@
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',
'c:\\program files (x86)\\google\\update\\googleupdate.exe',
"c:\\program files\\google\\chrome\\application\\chrome.exe",
"d:\\programs\\microsoft vs code\\code.exe",
"c:\\windows\\temp\\inv4cdf_tmp\\invcol.exe",
"d:\\program files (x86)\\microsoft visual studio\\2019\\community\\common7\\ide\\devenv.exe",
"c:\\program files\\dell\\saremediation\\agent\\dellsupportassistremedationservice.exe",
"d:\\program files (x86)\\microsoft visual studio\\2019\\community\\common7\\ide\\extensions\\microsoft\\liveshare\\agent\\vsls-agent.exe",
"d:\\program files (x86)\\microsoft visual studio\\2019\\community\\common7\\servicehub\\controller\\microsoft.servicehub.controller.exe ",
"d:\\program files (x86)\\microsoft visual studio\\2019\\community\\vc\\tools\\msvc\\14.29.30133\\bin\\hostx86\\x64\\cl.exe",
"c:\\program files\\git\\mingw64\\bin\\git.exe",
"c:\\windows\\system32\\usoclient.exe",
"c:\\windows\\system32\\winlogon.exe",
"c:\\windows\\system32\\userinit.exe",
"c:\\windows\\system32\\dwm.exe",
"c:\\windows\\system32\\compattelrunner.exe",
"c:\\windows\\system32\\searchindexer.exe",
"c:\\windows\\system32\\searchprotocolhost.exe",
'c:\\windows\\system32\\runtimebroker.exe',
'c:\\windows\\system32\\backgroundtaskhost.exe',
'c:\\program files\\dell\\supportassistagent\\pcd\\supportassist\\dsapi.exe',
'c:\\program files\\dell\\supportassistagent\\pcd\\supportassist\\systemidlecheck.exe',
'c:\\program files (x86)\\microsoft\\edgeupdate\\microsoftedgeupdate.exe',
'c:\\program files\\common files\\mcafee\\platform\\core\\mchost.exe',
'c:\\windows\\system32\\mmc.exe',
'c:\\program files (x86)\\microsoft\\edge\\application\\101.0.1210.53\\identity_helper.exe',
'c:\\windows\\system32\\audiodg.exe',
'c:\\windows\\system32\\smartscreen.exe',
'c:\\program files\\rmroot\\rm_service.exe',
'c:\\windows\\immersivecontrolpanel\\systemsettings.exe',
'c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe',
'c:\\users\\localhost\\appdata\\local\\programs\\microsoft vs code\\code.exe',
'c:\\program files (x86)\\aliwangwang\\aliim.exe',
'c:\\program files\\git\\cmd\\git.exe',
'c:\\windows\\system32\\taskmgr.exe',
'd:\\tools\\microsoft vs code\\code.exe']
trust_list = [
['node.exe', 'wmic.exe', 'conhost.exe', 'powershell.exe'],
['tqclient.exe', 'wscavctrl.exe', 'regsvr32.exe', 'dumpuper.exe'],
['tqclient.exe', 'tqassetregister.exe', 'wscavctrl.exe'],
['code.exe', 'conhost.exe', 'bash.exe', 'powershell.exe', 'go.exe'],
['code.exe', 'conhost.exe', 'bash.exe', 'powershell.exe'],
['explorer.exe', 'thunder.exe', 'xlliveud.exe', 'aplayer.exe'],
['ddvdatacollector.exe',
'atiw.exe'],
]
skip_md5 = [
'82bcb342bce193dfe1740a13bce62e81',
'406b23ca616e3ba6cf6033934ff073fc',
'c9d7fa5d48de4f3b9615595c336f6bdb',
'0cff71e27df7f00fb1f029920bd8869a',
'249a55048751d0c77446657437c342b7',
'452012f093d716c17c5cf93e31dd075a',
'b8ba559709e05485ce9ee39c5a028e30',
'cb83db7acb08ccd0370200eed9a1803b',
'cde7786dba838941e42814f611be4fcd',
'0b50aa0f894d6a65f3fd749cb0c6a5f2',
'0d46559e826c8a7b5d432d0a91954ba2',
'c07447a5b870e76bafa14ea2b39282c2',
'b55ad19c6c110e9bf985bc8674f7bcb3',
'c69459ddbf5c2114bfd70b170b8807e0',
'c8e806dd1d44c6993b6d85fa77d9f89f',
'b9dca65ce1540b8679bc9112ea100032',
'f7017525f394d84ce1b727f50244a9ce',
'32275787c7c51d2310b8fe2facf2a935',
'1acf25a85a4e0a9b7da5d948ca2a69b4',
'84244433fa7b5b80d0b7f5abd88eb8d6',
'ebe463f5bc61aa2d44e698b6e06df705',
'f7c71796dab2a6077458e038d1274392'
]
root_process_path = ['c:\\windows\system32\\services.exe',
'c:\\windows\system32\\svchost.exe',
'c:\\windows\\explorer.exe',
'c:\\windows\\system32\\wbem\\wmiprvse.exe']
g_ProcessChainList = []
# chain
# -> (pid,ppid)
# -> (pid,ppid)
class Process:
def __init__(self, pid, ppid, path, params, time, md5, user, host):
self.pid = pid
self.ppid = ppid
self.path = path
self.params = params
self.chain_hash = ''
self.active = True
self.operationlist = {}
self.risk_score = 0
self.terminate = False
self.rmpid = tools.get_md5(
str(pid) + str(ppid) + path + params + str(time))
self.time = time
self.rmppid = ""
self.root_rmpid = ""
self.md5 = md5
self.user = user
self.chain: ProcessChain = None
self.host = host
def set_chain_data(self, chain):
self.chain = chain
def set_chain_hash(self, chain_hash):
self.chain_hash = chain_hash
def set_root_rmpid(self, root_rmpid):
self.root_rmpid = root_rmpid
def set_rmppid(self, rmppid):
self.rmppid = rmppid
def set_score(self, new_score, opertion):
if opertion not in self.operationlist:
self.risk_score += new_score
self.operationlist[opertion] = 1
else:
self.operationlist[opertion] += 1
if opertion not in self.chain.operationlist:
self.chain.risk_score += new_score
self.chain.operationlist[opertion] = 1
else:
self.chain.operationlist[opertion] += 1
class ProcessChain:
def __init__(self, root_process: Process):
# 这样的话 无论几分钟读取都是固定关掉chain hash
self.hash = tools.get_md5(root_process.rmpid + str(root_process.time))
self.root_process_rmid = root_process.rmpid
self.root_process = root_process
self.active_count = 0
self.terminate_count = 0
self.risk_score = 0
self.operationlist = {}
self.process_list = []
self.json_arrays = []
self.active = True
self.rpc = False
self.rpc_process_chain = ""
self.time = root_process.time
self.host = root_process.host
self.add_root_process(root_process)
def get_operationlist(self):
return self.operationlist
def find_process_by_pid(self, pid):
for iter in self.process_list:
process_item: Process = iter
if process_item.pid == pid and process_item.active:
return process_item
return None
def add_root_process(self, root_process: Process):
root_process.set_chain_hash(self.hash)
root_process.set_rmppid(root_process.rmpid)
root_process.set_chain_data(self)
self.process_list.append(root_process)
self.active_count += 1
def add_process(self, new_process: Process, new_ppid):
parent_process = self.find_process_by_pid(new_ppid)
if parent_process is None:
return
new_process.set_rmppid(parent_process.rmpid)
new_process.set_chain_hash(self.hash)
new_process.set_root_rmpid(self.root_process_rmid)
new_process.set_chain_data(self)
self.process_list.append(new_process)
self.active_count += 1
def terminal_process(self, terminal_pid):
process = self.find_process_by_pid(terminal_pid)
if process is None:
return
process.terminate = True
self.terminate_count += 1
if self.terminate_count == self.active_count:
self.active = False
def print_node(self, node, level):
print((" " * level) + "|--" +
node["path"] + " 进程pid: (" + str(node["pid"]) + ") 进程ppid: (" + str(node["ppid"]) + ")进程命令行: (" + node["params"] + ") 进程hash: (" + str(node["md5"]) + ") 触发规则: " + str(node["operationlist"]) + " 进程活动:" + str(node['active']))
for child in node["children"]:
self.print_node(child, level + 1)
def save_to_json(self, node):
self.json_arrays = node
def get_json(self):
return json.dumps({'process_node': self.json_arrays})
def clear_json(self):
self.json_arrays = []
def print_process(self):
self.print_node(self.json_arrays, 0)
def update_process_tree(self):
# print('========================================================')
# print('进程链hash: {} 进程链等级: {} 触发的行为列表 {}'.format(
# self.hash, self.risk_score, self.operationlist))
pid_nodes = []
for proc_info in self.process_list:
node = [info for info in pid_nodes if info["rmpid"] ==
proc_info.rmpid]
node = node[0] if len(node) > 0 else None
parent_node = [
info for info in pid_nodes if info["rmpid"] == proc_info.rmppid]
parent_node = parent_node[0] if len(parent_node) > 0 else None
if node is None:
node = {
"path": proc_info.path,
"pid": proc_info.pid,
"ppid": proc_info.ppid,
"rmpid": proc_info.rmpid,
"rmppid": proc_info.rmppid,
"params": proc_info.params,
"operationlist": proc_info.operationlist,
"md5": proc_info.md5,
"active": proc_info.active,
"children": []
}
pid_nodes.append(node)
if parent_node is None and proc_info.rmppid != proc_info.rmpid:
target_info = next(
temp_info for temp_info in self.process_list if temp_info.rmpid == proc_info.rmppid)
parent_node = dict()
parent_node["active"] = target_info.active
parent_node["path"] = target_info.path
parent_node["ppid"] = target_info.ppid
parent_node["pid"] = target_info.pid
parent_node["rmpid"] = target_info.rmpid
parent_node["rmppid"] = target_info.rmppid
parent_node["md5"] = target_info.md5
parent_node["params"] = target_info.params
parent_node["operationlist"] = target_info.operationlist
parent_node["children"] = []
pid_nodes.append(parent_node)
if parent_node is not None and parent_node["rmpid"] != node["rmpid"]:
parent_node["children"].append(node)
# find root node in pid_nodes
root_node = next(
info for info in pid_nodes if info["rmpid"] == self.root_process_rmid)
#self.print_node(root_node, 0)
self.save_to_json(root_node)
def chain_in_trust_list(chain: ProcessChain):
# 整个进程链中如果存在(只是存在就行)这些进程就排除
global trust_list
global root_process_path
is_trust = True
for trust_process_array in trust_list:
is_trust = True
for trust_process in trust_process_array:
for iter in chain.process_list:
process: Process = iter
if process.path.find(trust_process) == -1 and process.path not in root_process_path:
is_trust = False
break
if is_trust == False:
break
if is_trust:
break
return is_trust
def create_chain(root_process: Process) -> ProcessChain:
global g_ProcessChainList
chain = ProcessChain(root_process)
g_ProcessChainList.append(chain)
return chain
def get_process_by_pid(pid) -> Process:
chain_item = get_process_chain_by_pid(pid)
if chain_item is None:
return None
return chain_item.find_process_by_pid(pid)
def set_process_terminal_by_pid(pid) -> None:
chain_item = get_process_chain_by_pid(pid)
if chain_item is None:
return
chain_item.terminal_process(pid)
def get_process_chain_by_pid(pid) -> ProcessChain:
for iter in g_ProcessChainList:
chain_item: ProcessChain = iter
if chain_item.active:
process_item = chain_item.find_process_by_pid(pid)
if process_item is not None:
return chain_item
return None

82
Server/rule.py Normal file
View File

@@ -0,0 +1,82 @@
import rule_engine
import rules.py.process as rule_process
import rules.py.action as rule_action
import plugin
g_sample_rule = {}
g_sample_rule['process'] = rule_process.rule
g_sample_rule['action'] = rule_action.rule
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))
return 0, ''
def calc_score_in_create_process(log):
global base_process_rules
for iter in base_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:
for rule in iter['rules']:
# 这是or
if rule.matches(log):
return iter['score'], iter['name']
return 0, ''
def init_rule():
global base_process_rules
global base_action_rules
global base_host_rules
for iter in g_sample_rule['process']:
temp_process_rules = []
for iter_i in iter['rules']:
print(iter_i)
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']:
temp_process_rules = []
for iter_i in iter['rules']:
print(iter_i)
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']:
temp_process_rules = []
for iter_i in iter['rules']:
print(iter_i)
temp_process_rules.append(rule_engine.Rule(
iter_i
))
base_host_rules.append(
{'name': iter['name'], 'score': iter['score'], 'rules': temp_process_rules})
'''
plugin.dispath_rule_init()
print('init rule done')

165
Server/rules/py/action.py Normal file
View File

@@ -0,0 +1,165 @@
rule = [
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe" and grantedaccess & 0x0010 and sourceimage =~ ".*rundll32.exe"',
],
'score': 300,
'name': '已知内存加载mimikazt行为'
},
{
'rules': [
'action == "processaccess" and targetimage =~ ".*lsass.exe"',
],
'score': 60,
'name': 'LSASS高权限访问'
},
{
'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,
'name': '异常进程访问'
},
{
'rules': [
'action == "processaccess" and sourceimage =~ ".*office16.*" and calltrace =~ ".*kernelbase\.dll.*"',
],
'score': 100,
'name': 'office异常进程内存'
},
{
'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.*"',
],
'score': 40,
'name': '不正常的进程访问'
},
{
'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_.*"',
],
'score': 300,
'name': '已知CobalStrike'
},
{
'rules': [
'action == "pipecreate" and pipename =~ ".*paexec.*"',
'action == "pipecreate" and pipename =~ ".*remcom.*"',
'action == "pipecreate" and pipename =~ ".*csexec.*"'
],
'score': 300,
'name': '已知内网横向工具'
},
{
'rules': [
'action == "pipecreate" and pipename =~ ".*lsadump.*"',
'action == "pipecreate" and pipename =~ ".*cachedump.*"',
'action == "pipecreate" and pipename =~ ".*wceservicepipe.*"'
],
'score': 300,
'name': '已知mimikazt内存dump'
},
# todo 懒得做详细的规则了.加油完善规则吧
{
'rules': [
'action == "createremotethread"',
],
'score': 60,
'name': '疑似远程线程注入'
},
{
'rules': [
'action == "filecreatestreamhash"',
],
'score': 100,
'name': '文件流创建'
},
{
'rules': [
'action == "registryadd"',
'action == "registryvalueSet"',
'action == "registryobjectSet"',
],
'score': 100,
'name': '可疑注册表访问'
},
{
'rules': [
'action == "dnsquery"',
],
'score': 30,
'name': 'DNS解析'
},
{
'rules': [
'action == "networkconnect"',
],
'score': 30,
'name': '可疑网络链接'
},
{
'rules': [
'action == "clipboardchange"',
],
'score': 30,
'name': '可疑剪切板访问'
},
{
'rules': [
'action == "processtampering"',
],
'score': 200,
'name': '进程执行流劫持'
},
{
'rules': [
'action == "filedeletedetected"',
],
'score': 50,
'name': '删除可执行文件'
},
{
'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"',
],
'score': 80,
'name': '在windows目录创建可执行文件'
},
{
'rules': [
'action == "filecreate" and targetfilename =~ "c:\\\\\\\\windows\\\\\\\\.*"',
],
'score': 50,
'name': '在C盘目录创建文件'
},
{
'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"',
],
'score': 30,
'name': '在appdata目录创建可执行文件'
},
{
'rules': [
'action == "filecreate"',
],
'score': 50,
'name': '创建可疑文件'
}
]

390
Server/rules/py/process.py Normal file
View File

@@ -0,0 +1,390 @@
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被执行'
},
]

327
Server/sql.py Normal file
View File

@@ -0,0 +1,327 @@
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
import time
# 引入sqlalchemy中相关模块
from sqlalchemy import create_engine, MetaData
from sqlalchemy import Column, Integer, String, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import delete
import json
g_engine = None
g_base = declarative_base()
g_metadata = None
g_rawdata_table = None
g_rawdata_table_ins = None
g_threat_table = None
g_threat_table_ins = None
class raw_process_log(g_base):
__tablename__ = "raw_process_log"
# 定义各字段
id = Column(Integer, primary_key=True)
# 主机ip
host = Column(String)
# 动作
# processcreate, processterminal
action = Column(String)
# 进程路径
path = Column(String)
# 进程pid
pid = Column(Integer)
# 父进程pid
ppid = Column(Integer)
# 目标文件路径(如果有)
target_path = Column(String)
# 目标进程路径(如果有)
target_image_path = Column(String)
# 目标进程pid(如果有)
target_image_pid = Column(Integer)
# 目标的hash(如果有)
target_hash = Column(String)
# hash
hash = Column(String)
hit = Column(String)
score = Column(Integer)
chain_hash = Column(String)
type = Column(Integer)
# 时间戳
timestamp = Column(Integer)
commandline = Column(String)
user = Column(String)
# 原始字段
data = Column(String)
def __str__(self):
return self.id
class threat_log(g_base):
__tablename__ = "threat_log"
# 定义各字段
id = Column(Integer, primary_key=True)
# 主机ip
host = Column(String)
# 进程链hash,其他的为000000
process_chain_hash = Column(String)
# type
type = Column(Integer)
# 分数
risk_score = Column(Integer)
# 命中的规则
hit_rule = Column(String)
# json字段
data = Column(String)
# 时间戳
timestamp = Column(Integer)
# is end
is_end = Column(Integer)
# start process
start_process_info = Column(String)
# handle type
handle_type = Column(Integer)
def __str__(self):
return self.id
def init():
global g_engine
global g_base
global g_metadata
global g_rawdata_table
global g_rawdata_table_ins
global g_threat_table
global g_threat_table_ins
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)
g_rawdata_table_ins = g_rawdata_table.insert()
g_threat_table = Table("threat_log", g_metadata, autoload=True)
g_threat_table_ins = g_threat_table.insert()
def push_process_raw(
host,
log,
rule_hit_name,
score,
chain_hash,
type,
parent_pid,
target_pid,
self_hash,
target_image_path,
target_hash,
commandline,
user,
):
global g_engine
global g_rawdata_table
global g_rawdata_table_ins
timestamp = int(round(time.time() * 1000))
# 偷懒了 有时间再重构
ins = g_rawdata_table_ins.values(
host=host,
action=log["Action"],
path=log["Data"]["Path"]
if "Path" in log["Data"]
else (
log["Data"]["SourceImage"]
if "SourceImage" in log["Data"]
else (log["Data"]["Image"] if "Image" in log["Data"] else "")
), # 只有三种情况,没有path就找sourceimage,没有sourceimage就找image
pid=log["Data"]["ProcessId"],
ppid=parent_pid,
target_path=log["Data"]["TargetImage"]
if "TargetImage" in log["Data"]
else target_image_path,
target_image_path=log["Data"]["TargetFilename"]
if "TargetFilename" in log["Data"]
else "",
target_image_pid=target_pid,
target_hash=target_hash,
hash=self_hash,
data=json.dumps(log["Data"]),
timestamp=timestamp,
hit=rule_hit_name,
score=score,
chain_hash=chain_hash,
commandline=commandline,
user=user,
type=type,
)
# 连接引擎
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
return result
def select_create_process_raw_log_by_time(start, end):
global g_rawdata_table
sql_session = sessionmaker(bind=g_engine)
raw_log = (
sql_session()
.query(g_rawdata_table)
.filter(
raw_process_log.timestamp >= start,
raw_process_log.timestamp < end,
raw_process_log.action == "processcreate",
)
)
sql_session().close()
return raw_log
def select_threat_by_chain_id(host, process_chain_hash, type):
global g_threat_table
sql_session = sessionmaker(bind=g_engine)
threat = (
sql_session()
.query(g_threat_table)
.filter_by(host=host, process_chain_hash=process_chain_hash, type=type)
.all()
)
sql_session().close()
return threat
def update_threat_log(
host, risk_score, hit_rule_json, process_chain_hash, raw_json, type, is_end
):
global g_threat_table
global g_engine
conn = g_engine.connect()
update = (
g_threat_table.update()
.values(
risk_score=risk_score,
hit_rule=hit_rule_json,
data=raw_json,
is_end=int(is_end),
)
.where(
g_threat_table.columns.host == host,
g_threat_table.columns.process_chain_hash == process_chain_hash,
g_threat_table.columns.type == type,
)
)
result = conn.execute(update)
return result
def handle_threat_log(threat_id, handle_type):
global g_threat_table
global g_engine
conn = g_engine.connect()
update = (
g_threat_table.update()
.values(handle_type=handle_type, is_end=1)
.where(g_threat_table.columns.id == int(threat_id))
)
result = conn.execute(update)
return result
def delete_threat(threat_id):
global g_threat_table
global g_engine
conn = g_engine.connect()
result = conn.execute(
delete(g_threat_table).where(g_threat_table.columns.id == int(threat_id))
)
return result
def query_one_threat(threat_id):
global g_threat_table
sql_session = sessionmaker(bind=g_engine)
threat = sql_session().query(g_threat_table).filter_by(id=threat_id).first()
sql_session().close()
return threat
def query_all_threat_log(query_type):
global g_threat_table
sql_session = sessionmaker(bind=g_engine)
if int(query_type) == -1:
threat = (
sql_session()
.query(g_threat_table)
.with_entities(
threat_log.host,
threat_log.process_chain_hash,
threat_log.hit_rule,
threat_log.timestamp,
threat_log.type,
threat_log.risk_score,
threat_log.id,
threat_log.is_end,
threat_log.start_process_info,
threat_log.handle_type,
)
.all()
)
else:
threat = (
sql_session()
.query(g_threat_table)
.with_entities(
threat_log.host,
threat_log.process_chain_hash,
threat_log.hit_rule,
threat_log.timestamp,
threat_log.type,
threat_log.risk_score,
threat_log.id,
threat_log.is_end,
threat_log.start_process_info,
threat_log.handle_type,
)
.filter_by(handle_type=query_type)
.all()
)
sql_session().close()
return threat
def push_threat_log(
host,
risk_score,
hit_rule_json,
process_chain_hash,
raw_json,
type,
start_process_info,
):
global g_engine
global g_threat_table
global g_threat_table_ins
ins = g_threat_table_ins.values(
host=host,
risk_score=risk_score,
process_chain_hash=process_chain_hash,
hit_rule=hit_rule_json,
type=type,
data=raw_json,
timestamp=int(round(time.time() * 1000)),
is_end=0,
start_process_info=start_process_info,
handle_type=0,
)
# 连接引擎
conn = g_engine.connect()
# 执行语句
result = conn.execute(ins)
# print(raw_json)
return result

View File

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

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

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

View File

@@ -0,0 +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})}}]);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1 @@
"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}}]);

View File

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

Binary file not shown.

18
Server/tools.py Normal file
View File

@@ -0,0 +1,18 @@
import hashlib
import base64
def base64_deocde(str):
try:
return base64.b64decode(str).decode('utf-8')
except:
return ""
def get_md5(password):
# 1- 实例化加密对象
md5 = hashlib.md5()
# 2- 进⾏加密操作
md5.update(password.encode('utf-8'))
# 3- 返回加密后的结果
return md5.hexdigest()

151
Server/webserver.py Normal file
View File

@@ -0,0 +1,151 @@
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 = '>.}'
@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>')
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>')
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')
def plugin_menu():
if request.remote_addr not in config.ALLOW_ACCESS_IP:
return "Access Denied"
return {'data': {'menu': plugin.dispath_html_menu()}}
@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}
@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):
return "Access Denied"
sql.handle_threat_log(id, handletype)
return {'data': {'success': 1}}
@app.route('/api/v1/get/process_chain/delete', methods=['GET'])
def delete_chain_data():
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}}
@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')
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]
}
return {'data': return_data}
@app.route('/api/v1/get/process_chain/all')
def process_chain():
# -1全部 0未处理的 1处理的 2忽略的
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}
@app.route('/api/v1/process', methods=['POST'])
def process():
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)
return {'status': 'success'}
if __name__ == '__main__':
plugin.reload_plugs()
sql.init()
rule.init_rule()
# 如果你觉得日志太多了,去掉这个注释...
flask_log = logging.getLogger('werkzeug')
flask_log.setLevel(logging.ERROR)
app.run(debug=True, host="0.0.0.0")

1874
provider.json Normal file

File diff suppressed because it is too large Load Diff

1293
sysmon.xml Normal file

File diff suppressed because it is too large Load Diff