Skip to content

Commit 920a9b2

Browse files
authored
Merge pull request #106 from intelowlproject/develop
updated readme and peframe
2 parents d82f351 + bb50898 commit 920a9b2

File tree

12 files changed

+97
-362
lines changed

12 files changed

+97
-362
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,6 @@ Main features:
8080
* CloudFlare DoH Malware
8181
* Classic DNS resolution
8282

83-
### Documentation
84-
[![Documentation Status](https://readthedocs.org/projects/intelowl/badge/?version=latest)](https://intelowl.readthedocs.io/en/latest/?badge=latest)
85-
86-
Documentation about IntelOwl installation and usage can be found at https://intelowl.readthedocs.io/.
87-
88-
8983
### Legal notice
9084
You as a user of this project must review, accept and comply with the license
9185
terms of each downloaded/installed package listed below. By proceeding with the
@@ -108,6 +102,16 @@ license terms.
108102
[Intezer Yara sigs](https://github.com/intezer/yara-rules),
109103
[McAfee Yara sigs](https://github.com/advanced-threat-research/Yara-Rules)
110104

105+
### Documentation
106+
[![Documentation Status](https://readthedocs.org/projects/intelowl/badge/?version=latest)](https://intelowl.readthedocs.io/en/latest/?badge=latest)
107+
108+
Documentation about IntelOwl installation and usage can be found at https://intelowl.readthedocs.io/.
109+
110+
### Blog posts
111+
[v. 1.0.0 Announcement](https://www.honeynet.org/?p=7558)
112+
113+
[First announcement](https://www.certego.net/en/news/new-year-new-tool-intel-owl/)
114+
111115
### Acknowledgments
112116
This project was created and will be upgraded thanks to the following organizations:
113117

@@ -120,7 +124,6 @@ The project was accepted to the GSoC 2020 under the Honeynet Project!!
120124

121125
Stay tuned for upcoming [new features](https://www.honeynet.org/gsoc/gsoc-2020/google-summer-of-code-2020-project-ideas/#intel-owl-improvements) developed by Eshaan Bansal ([Twitter](https://twitter.com/mask0fmydisguis)).
122126

123-
124127
### About the author
125128
Feel free to contact the author at any time:
126129
Matteo Lodi ([Twitter](https://twitter.com/matte_lodi))

api_app/script_analyzers/file_analyzers/peframe.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import requests
22
import traceback
3+
import json
34
import logging
45
import time
56

@@ -10,26 +11,40 @@
1011

1112

1213
def run(analyzer_name, job_id, filepath, filename, md5, additional_config_params):
13-
logger.info("started analyzer {} job_id {}" "".format(analyzer_name, job_id))
14+
logger.info(f"started analyzer {analyzer_name} job_id {job_id}")
1415
report = general.get_basic_report_template(analyzer_name)
1516
try:
1617
# get binary
1718
binary = general.get_binary(job_id)
18-
# run analysis
19-
files = {"file": binary}
20-
r = requests.post("http://peframe:4000/run_analysis", files=files)
21-
r_data = r.json()
22-
if r.status_code == 200:
23-
max_tries = additional_config_params.get("max_tries", 15)
24-
res = _poll_for_result(job_id, r_data["md5"], max_tries)
25-
else:
26-
raise AnalyzerRunException(r_data["error"])
19+
# request new analysis
20+
req_data = {"args": ["-j", "@filetoscan"]}
21+
req_files = {"filetoscan": binary}
22+
r = requests.post("http://peframe:4000/peframe", files=req_files, data=req_data)
23+
# handle cases in case of error
24+
if r.status_code == 404:
25+
raise AnalyzerRunException("PEframe docker container is not running.")
26+
if r.status_code == 400:
27+
err = r.json()["error"]
28+
raise AnalyzerRunException(err)
29+
if r.status_code == 500:
30+
raise AnalyzerRunException(
31+
"Internal Server Error in PEframe docker container"
32+
)
33+
# just in case error is something else
34+
r.raise_for_status()
2735

36+
max_tries = additional_config_params.get("max_tries", 15)
37+
r_data = r.json()
38+
resp = _poll_for_result(job_id, r_data["key"], max_tries)
2839
# limit the length of the strings dump
29-
if "strings" in res and "dump" in res["strings"]:
30-
res["strings"]["dump"] = res["strings"]["dump"][:100]
40+
result = resp.get("report", None)
41+
if result:
42+
result = json.loads(result)
43+
if "strings" in result and "dump" in result["strings"]:
44+
result["strings"]["dump"] = result["strings"]["dump"][:100]
3145

32-
report["report"] = res
46+
# set final report
47+
report["report"] = result
3348
except AnalyzerRunException as e:
3449
error_message = (
3550
f"job_id:{job_id} analyzer:{analyzer_name}"
@@ -89,8 +104,8 @@ def _poll_for_result(job_id, hash, max_tries):
89104
)
90105

91106

92-
def _query_for_result(hash):
107+
def _query_for_result(key):
93108
headers = {"Accept": "application/json"}
94-
resp = requests.get(f"http://peframe:4000/get_report/{hash}", headers=headers)
109+
resp = requests.get(f"http://peframe:4000/peframe?key={key}", headers=headers)
95110
data = resp.json()
96111
return resp.status_code, data

api_app/script_analyzers/yara_repo_downloader.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ sed -i "/MalConfScan.yar/d" $community_yara_index
3030
sed -i "/RAT_PoetRATPython.yar/d" $community_yara_index
3131
sed -i "/Email_fake_it_maintenance_bulletin.yar/d" $community_yara_index
3232
sed -i "/Email_quota_limit_warning.yar/d" $community_yara_index
33+
sed -i "/RANSOM_acroware.yar/d" $community_yara_index
34+
sed -i "/TOOLKIT_THOR_HackTools.yar/d" $community_yara_index
3335

3436
# Florian Roth rules
3537
git clone https://github.com/Neo23x0/signature-base.git

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
author = "Matteo Lodi"
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = "0.2.0"
25+
release = "1.0.0"
2626

2727

2828
# -- General configuration ---------------------------------------------------

env_file_integrations_template

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@
33
### IMPORTANT: don't change these values unless you know what you are doing. It can have breaking changes!
44

55
# PEframe
6-
FLASK_SECRET_KEY=
7-
FLASK_DEBUG=False
86
WORKERS=1
97
LOG_LEVEL=INFO

integrations/peframe/app.py

Lines changed: 15 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,232 +1,46 @@
11
# system imports
22
import os
3-
import subprocess
4-
import shutil
5-
import hashlib
6-
import json
7-
import time
83
import logging
94

105
# web imports
11-
from http import HTTPStatus
12-
from flask import Flask, request, jsonify, make_response
13-
from flask_sqlalchemy import SQLAlchemy
6+
from flask import Flask
147
from flask_executor import Executor
15-
from werkzeug.utils import secure_filename
8+
from flask_shell2http import Shell2HTTP
169

1710
# Globals
1811
app = Flask(__name__)
1912
executor = Executor(app)
13+
shell2http = Shell2HTTP(app, executor)
14+
15+
# with this, we can make http calls to the endpoint: /peframe
16+
shell2http.register_command(endpoint="peframe", command_name="peframe")
2017

2118
# Config
2219
CONFIG = {
23-
"SECRET_KEY": os.environ.get("FLASK_SECRET_KEY")
24-
or __import__("secrets").token_hex(16),
25-
"UPLOAD_PATH": os.environ.get("UPLOAD_PATH") or "uploads/",
26-
"SQLALCHEMY_DATABASE_URI": os.environ.get("DATABASE_URL") or "sqlite:///site.db",
27-
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
28-
"DEBUG": os.environ.get("FLASK_DEBUG") or False,
20+
"SECRET_KEY": __import__("secrets").token_hex(16),
2921
}
3022
app.config.update(CONFIG)
3123

32-
# SQLAlchemy Models
33-
db = SQLAlchemy(app)
34-
35-
36-
class Result(db.Model):
37-
md5 = db.Column(db.String(128), primary_key=True, unique=True)
38-
timestamp = db.Column(db.Float(), default=time.time())
39-
report = db.Column(db.JSON(), nullable=True, default=None)
40-
error = db.Column(db.TEXT(), nullable=True, default=None)
41-
status = db.Column(db.String(20), nullable=True, default="failed")
42-
43-
44-
# Utility functions
45-
def call_peframe(f_loc, f_hash):
46-
"""
47-
This function is called by the executor to run peframe
48-
using a subprocess asynchronously.
49-
"""
50-
try:
51-
cmd = f"peframe -j {f_loc}"
52-
proc = subprocess.Popen(
53-
cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE
54-
)
55-
stdout, stderr = proc.communicate()
56-
stderr = stderr.decode("ascii")
57-
err = None if ("SyntaxWarning" in stderr) else stderr
58-
if err and stdout:
59-
status = "reported_with_fails"
60-
elif stdout and not err:
61-
status = "success"
62-
else:
63-
status = "failed"
64-
65-
# This gets stored as Future.result
66-
job_result = {
67-
"file_location": f_loc,
68-
"md5": f_hash,
69-
"stdout": json.dumps(json.loads(stdout)),
70-
"stderr": err,
71-
"status": status,
72-
}
73-
app.logger.info(f"job_{f_hash} was successful.")
74-
return job_result
75-
76-
except Exception as e:
77-
job_key = f"job_{f_hash}"
78-
app.logger.exception(f"Caught exception:{e}")
79-
executor.futures._futures.get(job_key).cancel()
80-
app.logger.error(f"{job_key} was cancelled")
81-
job_result = {
82-
"file_location": f_loc,
83-
"md5": f_hash,
84-
"stdout": None,
85-
"stderr": str(e),
86-
"status": "failed",
87-
}
88-
return job_result
89-
90-
91-
def add_result_to_db(future):
92-
"""
93-
Default callable fn for Future object.
94-
"""
95-
# get job result from future
96-
job_res = future.result()
97-
app.logger.debug(job_res)
98-
# get and update corresponding db row object
99-
result = Result.query.get(job_res.get("md5"))
100-
result.status = job_res.get("status")
101-
result.report = job_res.get("stdout")
102-
result.error = job_res.get("stderr")
103-
104-
# delete file
105-
os.remove(job_res.get("file_location"))
106-
# finally commit changes to DB
107-
db.session.commit()
108-
109-
110-
executor.add_default_done_callback(add_result_to_db)
111-
112-
113-
# API routes/endpoints
114-
@app.before_first_request
115-
def before_first_request():
116-
try:
117-
db.drop_all()
118-
db.create_all()
119-
app.logger.debug("Dropped current DB and created new instance")
120-
except Exception as e:
121-
app.logger.exception(f"Caught Exception:{e}")
122-
db.create_all()
123-
app.logger.debug("Created new DB instance")
124-
125-
_upload_path = app.config.get("UPLOAD_PATH")
126-
try:
127-
os.mkdir(_upload_path)
128-
except FileExistsError:
129-
app.logger.debug(f"Emptying upload_path:{_upload_path} folder.")
130-
shutil.rmtree(_upload_path, ignore_errors=True)
131-
os.mkdir(_upload_path)
132-
133-
134-
@app.route("/run_analysis", methods=["POST"])
135-
def run_analysis():
136-
try:
137-
# Check if file part exists
138-
if "file" not in request.files:
139-
app.logger.error("No file part in request")
140-
return make_response(jsonify(error="No File part"), HTTPStatus.NOT_FOUND)
141-
142-
# get file and save it
143-
req_file = request.files["file"]
144-
f_name = secure_filename(req_file.filename)
145-
f_loc = os.path.join(app.config.get("UPLOAD_PATH"), f_name)
146-
req_file.save(f_loc)
147-
148-
# Calc file hash
149-
with open(f_loc, "rb") as rf:
150-
f_hash = hashlib.md5(rf.read()).hexdigest()
151-
152-
# Check if hash already in DB, and return directly if yes
153-
res = Result.query.get(f_hash)
154-
if res:
155-
app.logger.info(f"Report already exists for md5:{f_hash}")
156-
return make_response(
157-
jsonify(info="Analysis already exists", status=res.status, md5=res.md5),
158-
200,
159-
)
160-
161-
app.logger.info(f"Analysis requested for md5:{f_hash}")
162-
163-
# add to DB
164-
result = Result(md5=f_hash, status="running")
165-
db.session.add(result)
166-
db.session.commit()
167-
168-
# run executor job in background
169-
job_key = f"job_{f_hash}"
170-
executor.submit_stored(
171-
future_key=job_key, fn=call_peframe, f_loc=f_loc, f_hash=f_hash
172-
)
173-
app.logger.info(f"Job created with key:{job_key}.")
174-
175-
return make_response(jsonify(status="running", md5=f_hash), 200)
176-
177-
except Exception as e:
178-
app.logger.exception(f"unexpected error {e}")
179-
return make_response(jsonify(error=str(e)), HTTPStatus.INTERNAL_SERVER_ERROR)
180-
181-
182-
@app.route("/get_report/<md5_to_get>", methods=["GET"])
183-
def ask_report(md5_to_get):
184-
try:
185-
app.logger.info(f"Report requested for md5:{md5_to_get}")
186-
# check if job has been finished
187-
future = executor.futures._futures.get(f"job_{md5_to_get}", None)
188-
if future:
189-
if future.done:
190-
# pop future object since it has been finished
191-
executor.futures.pop(f"job_{md5_to_get}")
192-
else:
193-
return make_response(jsonify(status="running", md5=md5_to_get), 200)
194-
# if yes, get result from DB
195-
res = Result.query.get(md5_to_get)
196-
if not res:
197-
raise Exception(f"Report does not exist for md5:{md5_to_get}")
198-
199-
return make_response(
200-
jsonify(
201-
status=res.status,
202-
md5=res.md5,
203-
report=json.loads(res.report),
204-
error=res.error,
205-
timestamp=res.timestamp,
206-
),
207-
200,
208-
)
209-
210-
except Exception as e:
211-
app.logger.exception(f"Caught Exception:{e}")
212-
return make_response(jsonify(error=str(e)), HTTPStatus.NOT_FOUND)
213-
21424

21525
# Application Runner
21626
if __name__ == "__main__":
21727
app.run(port=4000)
21828
else:
219-
# set logger
29+
# get flask-shell2http logger instance
30+
logger = logging.getLogger("flask_shell2http")
31+
# logger config
22032
formatter = logging.Formatter(
221-
"%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s"
33+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
22234
)
22335
log_level = os.getenv("LOG_LEVEL", logging.INFO)
22436
log_path = "/var/log/intel_owl"
37+
# create new file handlers
22538
fh = logging.FileHandler(f"{log_path}/peframe.log")
22639
fh.setFormatter(formatter)
22740
fh.setLevel(log_level)
228-
app.logger.addHandler(fh)
22941
fh_err = logging.FileHandler(f"{log_path}/peframe_errors.log")
23042
fh_err.setFormatter(formatter)
23143
fh_err.setLevel(logging.ERROR)
232-
app.logger.addHandler(fh_err)
44+
# set the logger
45+
logger.addHandler(fh)
46+
logger.addHandler(fh_err)

0 commit comments

Comments
 (0)