Merge branch 'main' into main

This commit is contained in:
chebatory 2022-07-15 16:48:28 +03:00 committed by GitHub
commit 3dcbd89979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 111 additions and 76 deletions

View File

@ -8,11 +8,11 @@ TinyCheck allows you to easily capture network communications from a smartphone
The idea of TinyCheck emerged in a meeting about stalkerware with a [French women's shelter](https://www.centre-hubertine-auclert.fr). During this meeting we talked about how to easily detect [stalkerware](https://stopstalkerware.org/) without installing very technical apps nor doing forensic analysis on the victim's smartphone. The initial concept was to develop a tiny kiosk device based on Raspberry Pi which can be used by non-tech people to test their smartphones against malicious communications issued by stalkerware or any spyware. The idea of TinyCheck emerged in a meeting about stalkerware with a [French women's shelter](https://www.centre-hubertine-auclert.fr). During this meeting we talked about how to easily detect [stalkerware](https://stopstalkerware.org/) without installing very technical apps nor doing forensic analysis on the victim's smartphone. The initial concept was to develop a tiny kiosk device based on Raspberry Pi which can be used by non-tech people to test their smartphones against malicious communications issued by stalkerware or any spyware.
Of course, TinyCheck can also be used to spot any malicious communications from cybercrime to state-sponsored implants. It allows the end-user to push his own extended Indicators of Compromise via a backend in order to detect some ghosts over the wire. Of course, TinyCheck can also be used to spot any malicious communications from cybercrime to state-sponsored implants. It allows the end-user to push their own extended Indicators of Compromise via a backend in order to detect some ghosts over the wire.
<p align="center"><strong>If you need more documentation on how to install it, use it and the internals, don't hesitate to take a look at the <a href="https://github.com/KasperskyLab/TinyCheck/wiki">TinyCheck Wiki</a>.</strong></p> <p align="center"><strong>If you need more documentation on how to install it, use it and the internals, don't hesitate to take a look at the <a href="https://github.com/KasperskyLab/TinyCheck/wiki">TinyCheck Wiki</a>.</strong></p>
<p align="center">If you have any question about the projet, want to contribute or just send your feedback, <br />don't hesitate to contact us at tinycheck[@]kaspersky[.]com.</p> <p align="center">If you have any question about the project, want to contribute or just send your feedback, <br />don't hesitate to contact us at tinycheck[@]kaspersky[.]com.</p>
### Use cases ### Use cases

View File

@ -16,60 +16,82 @@ import os
containing a capture.pcap file. containing a capture.pcap file.
""" """
if __name__ == "__main__":
if len(sys.argv) == 2:
capture_directory = sys.argv[1]
if os.path.isdir(capture_directory):
manager = Manager() def analyze(capture_directory,frontend=False):
alerts = manager.dict() if os.path.isdir(capture_directory):
def zeekengine(alerts): manager = Manager()
zeek = ZeekEngine(capture_directory) alerts = manager.dict()
zeek.start_zeek()
alerts["zeek"] = zeek.retrieve_alerts()
# whitelist.json writing. def zeekengine(alerts):
with open(os.path.join(capture_directory, "assets/whitelist.json"), "w") as f: zeek = ZeekEngine(capture_directory)
f.write(json.dumps(zeek.retrieve_whitelist(), zeek.start_zeek()
indent=4, separators=(',', ': '))) alerts["zeek"] = zeek.retrieve_alerts()
# conns.json writing. if not os.path.isdir(os.path.join(capture_directory, "assets")):
with open(os.path.join(capture_directory, "assets/conns.json"), "w") as f: os.mkdir(os.path.join(capture_directory, "assets"))
f.write(json.dumps(zeek.retrieve_conns(), # whitelist.json writing.
indent=4, separators=(',', ': '))) with open(os.path.join(capture_directory, "assets/whitelist.json"), "w") as f:
f.write(json.dumps(zeek.retrieve_whitelist(),
indent=4, separators=(',', ': ')))
def snortengine(alerts): # conns.json writing.
suricata = SuricataEngine(capture_directory) with open(os.path.join(capture_directory, "assets/conns.json"), "w") as f:
suricata.start_suricata() f.write(json.dumps(zeek.retrieve_conns(),
alerts["suricata"] = suricata.get_alerts() indent=4, separators=(',', ': ')))
# Start the engines. def snortengine(alerts):
p1 = Process(target=zeekengine, args=(alerts,)) suricata = SuricataEngine(capture_directory)
p2 = Process(target=snortengine, args=(alerts,)) suricata.start_suricata()
p1.start() alerts["suricata"] = suricata.get_alerts()
p2.start()
# Wait to their end. # Start the engines.
p1.join() p1 = Process(target=zeekengine, args=(alerts,))
p2.join() p2 = Process(target=snortengine, args=(alerts,))
p1.start()
p2.start()
# Some formating and alerts.json writing. # Wait to their end.
with open(os.path.join(capture_directory, "assets/alerts.json"), "w") as f: p1.join()
report = {"high": [], "moderate": [], "low": []} p2.join()
for alert in (alerts["zeek"] + alerts["suricata"]):
if alert["level"] == "High": # Some formating and alerts.json writing.
report["high"].append(alert) with open(os.path.join(capture_directory, "assets/alerts.json"), "w") as f:
if alert["level"] == "Moderate": report = {"high": [], "moderate": [], "low": []}
report["moderate"].append(alert) for alert in (alerts["zeek"] + alerts["suricata"]):
if alert["level"] == "Low": if alert["level"] == "High":
report["low"].append(alert) report["high"].append(alert)
f.write(json.dumps(report, indent=4, separators=(',', ': '))) if alert["level"] == "Moderate":
report["moderate"].append(alert)
if alert["level"] == "Low":
report["low"].append(alert)
f.write(json.dumps(report, indent=4, separators=(',', ': ')))
# Generate the report
report = Report(capture_directory,frontend)
report.generate_report()
# Generate the report
report = Report(capture_directory)
report.generate_report()
else:
print("The directory doesn't exist.")
else: else:
print("Please specify a capture directory in argument.") print("The directory doesn't exist.")
def usage():
print("""Usage: python analysis.py [capture_directory]
where [capture_directory] is a directory containing a capture.pcap file
analysis.py -f starts the analysis in frontend mode intended to be called by the TinyCheck frontend.""")
if __name__ == "__main__":
if len(sys.argv) == 2: #called manually without frontend
analyze(sys.argv[1], False)
elif len(sys.argv) == 3:
if(sys.argv[1]) == "-f": #frontend mode
analyze(sys.argv[2], True)
else:
usage()
else:
usage()

View File

@ -13,7 +13,7 @@ from utils import get_config
class Report(object): class Report(object):
def __init__(self, capture_directory): def __init__(self, capture_directory, frontend):
self.capture_directory = capture_directory self.capture_directory = capture_directory
self.alerts = self.read_json(os.path.join( self.alerts = self.read_json(os.path.join(
capture_directory, "assets/alerts.json")) capture_directory, "assets/alerts.json"))
@ -21,10 +21,13 @@ class Report(object):
capture_directory, "assets/whitelist.json")) capture_directory, "assets/whitelist.json"))
self.conns = self.read_json(os.path.join( self.conns = self.read_json(os.path.join(
capture_directory, "assets/conns.json")) capture_directory, "assets/conns.json"))
self.device = self.read_json(os.path.join( self.device = None
capture_directory, "assets/device.json")) self.capinfos = None
self.capinfos = self.read_json(os.path.join( if frontend:
capture_directory, "assets/capinfos.json")) self.device = self.read_json(os.path.join(
capture_directory, "assets/device.json"))
self.capinfos = self.read_json(os.path.join(
capture_directory, "assets/capinfos.json"))
try: try:
with open(os.path.join(self.capture_directory, "capture.pcap"), "rb") as f: with open(os.path.join(self.capture_directory, "capture.pcap"), "rb") as f:
self.capture_sha1 = hashlib.sha1(f.read()).hexdigest() self.capture_sha1 = hashlib.sha1(f.read()).hexdigest()
@ -204,16 +207,18 @@ class Report(object):
""" """
header = "<div class=\"header\">" header = "<div class=\"header\">"
header += "<div class=\"logo\"></div>" header += "<div class=\"logo\"></div>"
header += "<p><br /><strong>{}: {}</strong><br />".format(self.template["device_name"], if self.device is not None:
header += "<p><br /><strong>{}: {}</strong><br />".format(self.template["device_name"],
self.device["name"]) self.device["name"])
header += "{}: {}<br />".format(self.template["device_mac"], header += "{}: {}<br />".format(self.template["device_mac"],
self.device["mac_address"]) self.device["mac_address"])
header += "{} {}<br />".format(self.template["report_generated_on"], header += "{} {}<br />".format(self.template["report_generated_on"],
datetime.now().strftime("%d/%m/%Y - %H:%M:%S")) datetime.now().strftime("%d/%m/%Y - %H:%M:%S"))
header += "{}: {}s<br />".format(self.template["capture_duration"], if self.capinfos is not None:
self.capinfos["Capture duration"]) header += "{}: {}s<br />".format(self.template["capture_duration"],
header += "{}: {}<br />".format(self.template["packets_number"], self.capinfos["Capture duration"])
self.capinfos["Number of packets"]) header += "{}: {}<br />".format(self.template["packets_number"],
self.capinfos["Number of packets"])
header += "{}: {}<br />".format( header += "{}: {}<br />".format(
self.template["capture_sha1"], self.capture_sha1) self.template["capture_sha1"], self.capture_sha1)
header += "</p>" header += "</p>"

View File

@ -236,6 +236,7 @@ class ZeekEngine(object):
pass pass
try: # Domain history check. try: # Domain history check.
whois_record = whois.whois(c["resolution"]) whois_record = whois.whois(c["resolution"])
creation_date = whois_record.creation_date if type( creation_date = whois_record.creation_date if type(
whois_record.creation_date) is not list else whois_record.creation_date[0] whois_record.creation_date) is not list else whois_record.creation_date[0]
@ -247,6 +248,7 @@ class ZeekEngine(object):
"host": c["resolution"], "host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "ACT-02"}) "id": "ACT-02"})
except: except:
pass pass
@ -447,7 +449,6 @@ class ZeekEngine(object):
self.working_dir), shell=True).wait() self.working_dir), shell=True).wait()
sp.Popen("cd {} && mv *.log assets/".format(self.working_dir), sp.Popen("cd {} && mv *.log assets/".format(self.working_dir),
shell=True).wait() shell=True).wait()
self.fill_dns(self.working_dir + "/assets/") self.fill_dns(self.working_dir + "/assets/")
self.netflow_check(self.working_dir + "/assets/") self.netflow_check(self.working_dir + "/assets/")
self.ssl_check(self.working_dir + "/assets/") self.ssl_check(self.working_dir + "/assets/")

View File

@ -10,7 +10,7 @@ import os
from functools import reduce from functools import reduce
# I'm not going to use an ORM for that. # I'm not going to use an ORM for that.
parent = "/".join(sys.path[0].split("/")[:-1]) parent = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0])))[0]
conn = sqlite3.connect(os.path.join(parent, "tinycheck.sqlite3")) conn = sqlite3.connect(os.path.join(parent, "tinycheck.sqlite3"))
cursor = conn.cursor() cursor = conn.cursor()

View File

@ -1,5 +1,5 @@
{ {
"name": "tinycheck-backend", "name": "@kaspersky/tinycheck-backend",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -1,7 +1,6 @@
{ {
"name": "tinycheck-backend", "name": "@kaspersky/tinycheck-backend",
"version": "0.1.0", "version": "0.1.0",
"private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --copy --port=4201", "serve": "vue-cli-service serve --copy --port=4201",
"build": "vue-cli-service build", "build": "vue-cli-service build",

View File

@ -1,5 +1,5 @@
{ {
"name": "tinycheck-new", "name": "@kaspersky/tinycheck-new",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -1,7 +1,6 @@
{ {
"name": "tinycheck-new", "name": "@kaspersky/tinycheck-new",
"version": "0.1.0", "version": "0.1.0",
"private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --copy --port=4202", "serve": "vue-cli-service serve --copy --port=4202",
"build": "vue-cli-service build", "build": "vue-cli-service build",

View File

@ -16,3 +16,4 @@ qrcode
netifaces netifaces
weasyprint weasyprint
python-whois python-whois
six

View File

@ -52,7 +52,8 @@ frontend:
user_lang: userlang user_lang: userlang
update: updateoption update: updateoption
choose_net: false choose_net: false
http_port: 80
# NETWORK - # NETWORK -
# Some elements related to the network configuration, such as # Some elements related to the network configuration, such as
# the interfaces (updated during the install), the list of SSIDs # the interfaces (updated during the install), the list of SSIDs
@ -77,6 +78,6 @@ network:
watchers: watchers:
iocs: iocs:
- https://raw.githubusercontent.com/KasperskyLab/TinyCheck/main/assets/iocs.json - https://raw.githubusercontent.com/KasperskyLab/TinyCheck/main/assets/iocs.json
- https://raw.githubusercontent.com/Te-k/stalkerware-indicators/master/indicators-for-tinycheck.json - https://raw.githubusercontent.com/Te-k/stalkerware-indicators/master/generated/indicators-for-tinycheck.json
whitelists: whitelists:
- https://raw.githubusercontent.com/KasperskyLab/TinyCheck/main/assets/whitelist.json - https://raw.githubusercontent.com/KasperskyLab/TinyCheck/main/assets/whitelist.json

View File

@ -265,6 +265,7 @@ change_hostname() {
install_package() { install_package() {
# Install associated packages by using aptitude. # Install associated packages by using aptitude.
if [[ $1 == "dnsmasq" || $1 == "hostapd" || $1 == "tshark" || $1 == "sqlite3" || $1 == "unclutter" || $1 == "swig" || $1 == "curl" ]]; then if [[ $1 == "dnsmasq" || $1 == "hostapd" || $1 == "tshark" || $1 == "sqlite3" || $1 == "unclutter" || $1 == "swig" || $1 == "curl" ]]; then
apt-get install $1 -y apt-get install $1 -y
elif [[ $1 == "suricata" ]];then elif [[ $1 == "suricata" ]];then
add-apt-repository ppa:oisf/suricata-stable add-apt-repository ppa:oisf/suricata-stable
@ -323,9 +324,9 @@ check_dependencies() {
"/usr/bin/unclutter" "/usr/bin/unclutter"
"/usr/bin/sqlite3" "/usr/bin/sqlite3"
"/usr/bin/pip" "/usr/bin/pip"
"/usr/bin/swig" "/usr/bin/swig"
"/usr/sbin/dhcpcd" "/usr/sbin/dhcpcd"
"/usr/bin/curl") "/usr/bin/curl")
echo -e "\e[39m[+] Checking dependencies...\e[39m" echo -e "\e[39m[+] Checking dependencies...\e[39m"
for bin in "${bins[@]}" for bin in "${bins[@]}"
@ -420,6 +421,7 @@ check_interfaces(){
do do
if echo "$iface" | grep -Eq "(wlan[0-9]|wl[a-z0-9]{,20})"; then if echo "$iface" | grep -Eq "(wlan[0-9]|wl[a-z0-9]{,20})"; then
config="$(ip a s $iface)" # Get the iface logic configuration config="$(ip a s $iface)" # Get the iface logic configuration
if echo "$config" | grep -qv "inet "; then # Test if not currently connected if echo "$config" | grep -qv "inet "; then # Test if not currently connected
hw="$(iw $iface info | grep wiphy | cut -d" " -f2)" # Get the iface hardware id. hw="$(iw $iface info | grep wiphy | cut -d" " -f2)" # Get the iface hardware id.
info="$(iw phy$hw info)" # Get the iface hardware infos. info="$(iw phy$hw info)" # Get the iface hardware infos.

View File

@ -24,7 +24,7 @@ class Analysis(object):
if self.token is not None: if self.token is not None:
parent = "/".join(sys.path[0].split("/")[:-2]) parent = "/".join(sys.path[0].split("/")[:-2])
sp.Popen( sp.Popen(
[sys.executable, "{}/analysis/analysis.py".format(parent), "/tmp/{}".format(self.token)]) [sys.executable, "{}/analysis/analysis.py".format(parent), "-f", "/tmp/{}".format(self.token)])
return {"status": True, return {"status": True,
"message": "Analysis started", "message": "Analysis started",
"token": self.token} "token": self.token}

View File

@ -46,7 +46,12 @@ app.register_blueprint(misc_bp, url_prefix='/api/misc')
app.register_blueprint(update_bp, url_prefix='/api/update') app.register_blueprint(update_bp, url_prefix='/api/update')
if __name__ == '__main__': if __name__ == '__main__':
port = ""
try:
port = int(read_config(("frontend", "http_port")))
except:
port = 80
if read_config(("frontend", "remote_access")): if read_config(("frontend", "remote_access")):
app.run(host="0.0.0.0", port=80) app.run(host="0.0.0.0", port=port)
else: else:
app.run(port=80) app.run(port=port)