Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/undo-ransomware/ransomware_detection.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMatthias Held <ilovemilk@wusa.io>2018-06-18 15:14:17 +0300
committerMatthias Held <ilovemilk@wusa.io>2018-06-18 15:14:17 +0300
commit0d4208bd4934d83654fc3893867b2557546b404a (patch)
treeb6db2416bb0da30e119fdf8ff2120dea7d086481 /lib
parent7a756a94ab887209f7ad7ffc6a01e2d16d01bfd4 (diff)
Add Nextcloud application
Diffstat (limited to 'lib')
-rw-r--r--lib/Analyzer/EntropyAnalyzer.php195
-rw-r--r--lib/Analyzer/EntropyFunnellingAnalyzer.php166
-rw-r--r--lib/Analyzer/EntropyFunnellingResult.php155
-rw-r--r--lib/Analyzer/EntropyResult.php106
-rw-r--r--lib/Analyzer/FileCorruptionAnalyzer.php103
-rw-r--r--lib/Analyzer/FileCorruptionResult.php75
-rw-r--r--lib/Analyzer/FileNameAnalyzer.php140
-rw-r--r--lib/Analyzer/FileNameResult.php107
-rw-r--r--lib/Analyzer/FileTypeFunnellingAnalyzer.php127
-rw-r--r--lib/Analyzer/SequenceAnalyzer.php194
-rw-r--r--lib/Analyzer/SequenceOperationsAnalyzer.php90
-rw-r--r--lib/Analyzer/SequenceResult.php178
-rw-r--r--lib/Analyzer/SequenceSizeAnalyzer.php78
-rw-r--r--lib/AppInfo/Application.php171
-rw-r--r--lib/BackgroundJob/CleanUpJob.php55
-rw-r--r--lib/CacheWrapper.php67
-rw-r--r--lib/Classifier.php105
-rw-r--r--lib/Connector/Sabre/RequestPlugin.php192
-rw-r--r--lib/Controller/ApiController.php367
-rw-r--r--lib/Controller/RecoverController.php60
-rw-r--r--lib/Db/FileOperation.php91
-rw-r--r--lib/Db/FileOperationMapper.php156
-rw-r--r--lib/Entropy/Entropy.php86
-rw-r--r--lib/FileSignatureList.php211
-rw-r--r--lib/Monitor.php462
-rw-r--r--lib/Notification/Notifier.php99
-rw-r--r--lib/Service/FileOperationService.php150
-rw-r--r--lib/Settings/Admin.php97
-rw-r--r--lib/Settings/AdminSection.php88
-rw-r--r--lib/Settings/Personal.php109
-rw-r--r--lib/Settings/PersonalSection.php97
-rw-r--r--lib/StorageWrapper.php259
32 files changed, 4636 insertions, 0 deletions
diff --git a/lib/Analyzer/EntropyAnalyzer.php b/lib/Analyzer/EntropyAnalyzer.php
new file mode 100644
index 0000000..d4f2699
--- /dev/null
+++ b/lib/Analyzer/EntropyAnalyzer.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Entropy\Entropy;
+use OCP\Files\File;
+use OCP\Files\IRootFolder;
+use OCP\Files\Storage\IStorage;
+use OCP\Files\NotFoundException;
+use OCP\ILogger;
+
+class EntropyAnalyzer
+{
+ /**
+ * Entropy cut-off point between normal and compressed or encrypted files.
+ *
+ * @var float
+ */
+ const ENTROPY_CUT_OFF = 7.69;
+
+ /**
+ * Size of the data blocks in bytes.
+ *
+ * @var int
+ */
+ const BLOCK_SIZE = 256;
+
+ /**
+ * Standard deviation cut-off point between compressed and encrypted files.
+ *
+ * @var float
+ */
+ const SD_CUT_OFF = 0.06;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IRootFolder */
+ protected $rootFolder;
+
+ /** @var Entropy */
+ protected $entropy;
+
+ /** @var string */
+ protected $userId;
+
+ /**
+ * @param ILogger $logger
+ * @param IRootFolder $rootFolder
+ * @param Entropy $entropy
+ * @param int $userId
+ */
+ public function __construct(
+ ILogger $logger,
+ IRootFolder $rootFolder,
+ Entropy $entropy,
+ $userId
+ ) {
+ $this->logger = $logger;
+ $this->rootFolder = $rootFolder;
+ $this->entropy = $entropy;
+ $this->userId = $userId;
+ }
+
+ /**
+ * Classifies a file using entropy measurements. It first calculates the
+ * native entropy of the file to decide wether it's a normal file with
+ * low entropy or a compressed or encrypted file with high entropy.
+ *
+ * If the file is identified as class B, it measures the
+ * standard deviation of the entropy of all blocks with a size of 256 bytes.
+ * To decide if the file is compressed or encrypted.
+ *
+ * The results classifies the file in the following three classes:
+ * ENCRYPTED
+ * COMPRESSED
+ * NORMAL
+ *
+ * @param File $node
+ * @param IStorage $storage
+ *
+ * @return EntropyResult
+ */
+ public function analyze($node)
+ {
+ $entropy = $this->calculateEntropyOfFile($node);
+ if ($entropy > self::ENTROPY_CUT_OFF) {
+ $entropyArray = $this->createEntropyArrayFromFile($node, self::BLOCK_SIZE);
+ $standardDeviation = $this->calculateStandardDeviationOfEntropy($entropyArray);
+ if ($standardDeviation > self::SD_CUT_OFF) {
+ return new EntropyResult(EntropyResult::COMPRESSED, $entropy, $standardDeviation);
+ }
+
+ return new EntropyResult(EntropyResult::ENCRYPTED, $entropy, $standardDeviation);
+ }
+
+ return new EntropyResult(EntropyResult::NORMAL, $entropy, 0.0);
+ }
+
+ /**
+ * Creates an array with the entropy of the data blocks.
+ *
+ * @param File $node
+ * @param int $blockSize
+ *
+ * @return array
+ */
+ protected function createEntropyArrayFromFile($node, $blockSize)
+ {
+ $data = $node->getContent();
+ if (!$data) {
+ $this->logger->debug('createEntropyArrayFromFile: Getting data failed.', array('app' => Application::APP_ID));
+
+ return [];
+ }
+
+ return $this->createEntropyArrayFromData($data, $blockSize);
+ }
+
+ /**
+ * Creates an array with the entropy of the data blocks.
+ *
+ * @param string $data
+ * @param int $blockSize
+ *
+ * @return array
+ */
+ protected function createEntropyArrayFromData($data, $blockSize)
+ {
+ $entropyArray = array();
+ $size = strlen($data);
+
+ for ($i = 0; $i < $size / $blockSize; $i++) {
+ if ($size >= $i * $blockSize + $blockSize) {
+ $block = substr($data, $i * $blockSize, $blockSize);
+ $entropyArray[$i] = $this->entropy->calculateEntropy($block);
+ }
+ }
+
+ return $entropyArray;
+ }
+
+ /**
+ * Calculates standard deviation of the entropy of multiple data blocks.
+ *
+ * @param array $entropyArray
+ *
+ * @return float
+ */
+ protected function calculateStandardDeviationOfEntropy($entropyArray)
+ {
+ $sd = $this->entropy->sd($entropyArray);
+
+ return $sd;
+ }
+
+ /**
+ * Calculates the entropy of a file.
+ *
+ * @param File $node
+ *
+ * @return float
+ */
+ protected function calculateEntropyOfFile($node)
+ {
+ $data = $node->getContent();
+ if (!$data) {
+ $this->logger->debug('calculateEntropyOfFile: Getting data failed.', array('app' => Application::APP_ID));
+
+ return 0.0;
+ }
+
+ return $this->entropy->calculateEntropy($data);
+ }
+}
diff --git a/lib/Analyzer/EntropyFunnellingAnalyzer.php b/lib/Analyzer/EntropyFunnellingAnalyzer.php
new file mode 100644
index 0000000..7144d5c
--- /dev/null
+++ b/lib/Analyzer/EntropyFunnellingAnalyzer.php
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\Classifier;
+use OCP\ILogger;
+
+class EntropyFunnellingAnalyzer
+{
+ /** @var ILogger */
+ protected $logger;
+
+ /**
+ * @param ILogger $logger
+ */
+ public function __construct(
+ ILogger $logger
+ ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * The entropy funnelling can be divided into two parts:.
+ *
+ * 1. The range of the entropy of the deleted files must be wider
+ * than the range of the entropy of the written files without the info files.
+ * 2. The range of the standard deviation of the deleted files must be wider
+ * than the range of the standard deviation of the written files without the info files.
+ *
+ * Each of the indicators increase the entropy funnelling score by one.
+ *
+ * @param array $deletedFiles
+ * @param array $writtenFiles
+ *
+ * @return EntropyFunnellingResult
+ */
+ public function analyze($deletedFiles, $writtenFiles)
+ {
+ // prepare data
+ $entropyOfDeletedFiles = [];
+ $entropyOfWrittenFiles = [];
+ $standardDeviationOfDeletedFiles = [];
+ $standardDeviationOfWrittenFiles = [];
+ $numberOfInfoFiles = 0;
+
+ foreach ($deletedFiles as $deletedFile) {
+ array_push($entropyOfDeletedFiles, $deletedFile->getEntropy());
+ array_push($standardDeviationOfDeletedFiles, $deletedFile->getStandardDeviation());
+ }
+ foreach ($writtenFiles as $writtenFile) {
+ // remove the entropy of info files from $entropyOfWrittenFiles
+ if ($writtenFile->getSuspicionClass() === Classifier::NOT_SUSPICIOUS) {
+ if ($numberOfInfoFiles < SequenceAnalyzer::NUMBER_OF_INFO_FILES) {
+ $numberOfInfoFiles++;
+ break;
+ }
+ }
+ array_push($entropyOfWrittenFiles, $writtenFile->getEntropy());
+ array_push($standardDeviationOfWrittenFiles, $writtenFile->getStandardDeviation());
+ }
+
+ // analyze data
+ $entropyFunnellingResult = new EntropyFunnellingResult(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0);
+ $medianOfEntropyDeleted = $this->median($entropyOfDeletedFiles);
+ $medianOfEntropyWritten = $this->median($entropyOfWrittenFiles);
+ $entropyFunnellingResult->setMedianDeleted($medianOfEntropyDeleted);
+ $entropyFunnellingResult->setMedianWritten($medianOfEntropyWritten);
+
+ $entropyFunnellingScore = 0;
+
+ $rangeOfEntropyDeleted = $this->range($entropyOfDeletedFiles);
+ $rangeOfStandardDeviationDeleted = $this->average($standardDeviationOfDeletedFiles);
+ $rangeOfEntropyWritten = $this->range($entropyOfWrittenFiles);
+ $rangeOfStandardDeviationWritten = $this->average($standardDeviationOfWrittenFiles);
+ $entropyFunnellingResult->setRangeOfEntropyDeleted($rangeOfEntropyDeleted);
+ $entropyFunnellingResult->setRangeOfEntropyWritten($rangeOfEntropyWritten);
+ $entropyFunnellingResult->setRangeOfStandardDeviationDeleted($rangeOfStandardDeviationDeleted);
+ $entropyFunnellingResult->setRangeOfStandardDeviationWritten($rangeOfStandardDeviationWritten);
+
+ // range of $entropyOfDeletedFiles must be wider than range of $entropyOfWrittenFiles
+ if ($rangeOfEntropyDeleted > $rangeOfEntropyWritten) {
+ $entropyFunnellingScore = $entropyFunnellingScore + 1;
+ // range of $standardDeviationOfDeletedFiles must be wider than range of $standardDeviationOfWrittenFiles
+ if ($rangeOfStandardDeviationDeleted > $rangeOfStandardDeviationWritten) {
+ $entropyFunnellingScore = $entropyFunnellingScore + 1;
+ }
+ }
+
+ $entropyFunnellingResult->setEntropyFunnelling($entropyFunnellingScore);
+
+ return $entropyFunnellingResult;
+ }
+
+ /**
+ * Calculates the average of an array.
+ *
+ * @param array $array
+ *
+ * @return float
+ */
+ private function average($array)
+ {
+ if (is_array($array) && count($array) > 0) {
+ return array_sum($array) / count($array);
+ }
+
+ return 0.0;
+ }
+
+ /**
+ * Calculates the range of an array.
+ *
+ * @param array $array
+ *
+ * @return float
+ */
+ private function range($array)
+ {
+ if (is_array($array) && count($array) > 0) {
+ sort($array);
+
+ return $array[count($array) - 1] - $array[0];
+ }
+
+ return 0.0;
+ }
+
+ /**
+ * Calculates the median of an array.
+ *
+ * @param array $array
+ *
+ * @return float
+ */
+ public function median($array)
+ {
+ if (is_array($array) && count($array) > 0) {
+ $count = count($array);
+ sort($array);
+ $mid = floor(($count - 1) / 2);
+
+ return ($array[$mid] + $array[$mid + 1 - $count % 2]) / 2;
+ }
+
+ return 0.0;
+ }
+}
diff --git a/lib/Analyzer/EntropyFunnellingResult.php b/lib/Analyzer/EntropyFunnellingResult.php
new file mode 100644
index 0000000..a3c7325
--- /dev/null
+++ b/lib/Analyzer/EntropyFunnellingResult.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+class EntropyFunnellingResult
+{
+ /** @var float */
+ protected $medianWritten;
+
+ /** @var float */
+ protected $medianDeleted;
+
+ /** @var float */
+ protected $rangeOfEntropyWritten;
+
+ /** @var float */
+ protected $rangeOfEntropyDeleted;
+
+ /** @var float */
+ protected $rangeOfStandardDeviationWritten;
+
+ /** @var float */
+ protected $rangeOfStandardDeviationDeleted;
+
+ /** @var int */
+ protected $entropyFunnelling;
+
+ /**
+ * @param float $medianWritten
+ * @param float $medianDeleted
+ * @param float $rangeOfEntropyWritten
+ * @param float $rangeOfEntropyDeleted
+ * @param float $rangeOfStandardDeviationWritten
+ * @param float $rangeOfStandardDeviationDeleted
+ * @param int $entropyFunnelling
+ */
+ public function __construct(
+ $medianWritten,
+ $medianDeleted,
+ $rangeOfEntropyWritten,
+ $rangeOfEntropyDeleted,
+ $rangeOfStandardDeviationWritten,
+ $rangeOfStandardDeviationDeleted,
+ $entropyFunnelling
+ ) {
+ $this->medianWritten = $medianWritten;
+ $this->medianDeleted = $medianDeleted;
+ $this->rangeOfEntropyWritten = $rangeOfEntropyWritten;
+ $this->rangeOfEntropyDeleted = $rangeOfEntropyDeleted;
+ $this->rangeOfStandardDeviationWritten = $rangeOfStandardDeviationWritten;
+ $this->$rangeOfStandardDeviationDeleted = $rangeOfStandardDeviationDeleted;
+ $this->entropyFunnelling = $entropyFunnelling;
+ }
+
+ public function getMedianWritten()
+ {
+ return $this->medianWritten;
+ }
+
+ public function setMedianWritten($medianWritten)
+ {
+ $this->medianWritten = $medianWritten;
+ }
+
+ public function getMedianDeleted()
+ {
+ return $this->medianDeleted;
+ }
+
+ public function setMedianDeleted($medianDeleted)
+ {
+ $this->medianDeleted = $medianDeleted;
+ }
+
+ public function getRangeOfEntropyWritten()
+ {
+ return $this->rangeOfEntropyWritten;
+ }
+
+ public function setRangeOfEntropyWritten($rangeOfEntropyWritten)
+ {
+ $this->rangeOfEntropyWritten = $rangeOfEntropyWritten;
+ }
+
+ public function getRangeOfEntropyDeleted()
+ {
+ return $this->rangeOfEntropyDeleted;
+ }
+
+ public function setRangeOfEntropyDeleted($rangeOfEntropyDeleted)
+ {
+ $this->rangeOfEntropyDeleted = $rangeOfEntropyDeleted;
+ }
+
+ public function getRangeOfStandardDeviationWritten()
+ {
+ return $this->rangeOfStandardDeviationWritten;
+ }
+
+ public function setRangeOfStandardDeviationWritten($rangeOfStandardDeviationWritten)
+ {
+ $this->rangeOfStandardDeviationWritten = $rangeOfStandardDeviationWritten;
+ }
+
+ public function getRangeOfStandardDeviationDeleted()
+ {
+ return $this->rangeOfStandardDeviationDeleted;
+ }
+
+ public function setRangeOfStandardDeviationDeleted($rangeOfStandardDeviationDeleted)
+ {
+ $this->rangeOfStandardDeviationDeleted = $rangeOfStandardDeviationDeleted;
+ }
+
+ public function getEntropyFunnelling()
+ {
+ return $this->entropyFunnelling;
+ }
+
+ public function setEntropyFunnelling($entropyFunnelling)
+ {
+ $this->entropyFunnelling = $entropyFunnelling;
+ }
+
+ public function toArray()
+ {
+ $var = get_object_vars($this);
+ foreach ($var as &$value) {
+ if (is_object($value) && method_exists($value, 'toArray')) {
+ $value = $value->toArray();
+ }
+ }
+
+ return $var;
+ }
+}
diff --git a/lib/Analyzer/EntropyResult.php b/lib/Analyzer/EntropyResult.php
new file mode 100644
index 0000000..29537d1
--- /dev/null
+++ b/lib/Analyzer/EntropyResult.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+class EntropyResult
+{
+ /**
+ * File classes.
+ *
+ * @var int
+ */
+ const ENCRYPTED = 1;
+ const COMPRESSED = 2;
+ const NORMAL = 3;
+
+ /** @var int */
+ private $fileClass;
+
+ /** @var float */
+ private $entropy;
+
+ /** @var float */
+ private $standardDeviation;
+
+ /**
+ * @param int $fileClass
+ * @param float $entropy
+ * @param float $standardDeviation
+ */
+ public function __construct(
+ $fileClass,
+ $entropy,
+ $standardDeviation
+ ) {
+ $this->fileClass = $fileClass;
+ $this->entropy = $entropy;
+ $this->standardDeviation = $standardDeviation;
+ }
+
+ /**
+ * @param int $fileClass
+ */
+ public function setFileClass($fileClass)
+ {
+ $this->fileClass = $fileClass;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFileClass()
+ {
+ return $this->fileClass;
+ }
+
+ /**
+ * @param float $entropy
+ */
+ public function setEntropy($entropy)
+ {
+ $this->entropy = $entropy;
+ }
+
+ /**
+ * @return float
+ */
+ public function getEntropy()
+ {
+ return $this->entropy;
+ }
+
+ /**
+ * @param float $standardDeviation
+ */
+ public function setStandardDeviation($standardDeviation)
+ {
+ $this->standardDeviation = $standardDeviation;
+ }
+
+ /**
+ * @return float
+ */
+ public function getStandardDeviation()
+ {
+ return $this->standardDeviation;
+ }
+}
diff --git a/lib/Analyzer/FileCorruptionAnalyzer.php b/lib/Analyzer/FileCorruptionAnalyzer.php
new file mode 100644
index 0000000..2d8058e
--- /dev/null
+++ b/lib/Analyzer/FileCorruptionAnalyzer.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\FileSignatureList;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\File;
+use OCP\ILogger;
+
+class FileCorruptionAnalyzer
+{
+ /** @var ILogger */
+ private $logger;
+
+ /** @var IRootFolder */
+ private $rootFolder;
+
+ /** @var string */
+ private $userId;
+
+ /**
+ * @param ILogger $logger
+ * @param IRootFolder $rootFolder
+ * @param string $userId
+ */
+ public function __construct(
+ ILogger $logger,
+ IRootFolder $rootFolder,
+ $userId
+ ) {
+ $this->logger = $logger;
+ $this->rootFolder = $rootFolder;
+ $this->userId = $userId;
+ }
+
+ /**
+ * Analysis a file if it's corrupted or not.
+ *
+ * @param File $node
+ * @return FileCorruptionResult
+ */
+ public function analyze($node)
+ {
+ return $this->isCorrupted($node);
+ }
+
+ /**
+ * Checks the file for existing file header informations and compares them,
+ * if found, to the file extension.
+ *
+ * @param File $node
+ * @return FileCorruptionResult
+ */
+ protected function isCorrupted(File $node)
+ {
+ $signatures = FileSignatureList::getSignatures();
+
+ try {
+ $data = $node->getContent();
+ foreach ($signatures as $signature) {
+ if (strtolower($signature['byteSequence']) === strtolower(bin2hex(substr($data, $signature['offset'], strlen($signature['byteSequence']) / 2)))) {
+ $pathInfo = pathinfo($node->getPath());
+ if (in_array(strtolower($pathInfo['extension']), $signature['extension'])) {
+ return new FileCorruptionResult(false, $signature['file_class']);
+ }
+
+ return new FileCorruptionResult(true);
+ }
+ }
+
+ return new FileCorruptionResult(true);
+ } catch (\OCP\Files\NotPermittedException $exception) {
+ $this->logger->debug('isCorrupted: Not permitted.', array('app' => Application::APP_ID));
+
+ return new FileCorruptionResult(false);
+ } catch (\OCP\Lock\LockedException $exception) {
+ $this->logger->debug('isCorrupted: File is locked.', array('app' => Application::APP_ID));
+
+ return new FileCorruptionResult(false);
+ }
+ }
+}
diff --git a/lib/Analyzer/FileCorruptionResult.php b/lib/Analyzer/FileCorruptionResult.php
new file mode 100644
index 0000000..9a1ca21
--- /dev/null
+++ b/lib/Analyzer/FileCorruptionResult.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+class FileCorruptionResult
+{
+ /** @var bool */
+ private $isCorrupted;
+
+ /** @var int */
+ private $fileClass;
+
+ /**
+ * @param bool $isCorrupted
+ * @param int $fileClass
+ */
+ public function __construct(
+ $isCorrupted,
+ $fileClass = -1
+ ) {
+ $this->isCorrupted = $isCorrupted;
+ $this->fileClass = $fileClass;
+ }
+
+ /**
+ * @param bool $isCorrupted
+ */
+ public function setCorrupted($isCorrupted)
+ {
+ $this->isCorrupted = $isCorrupted;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isCorrupted()
+ {
+ return $this->isCorrupted;
+ }
+
+ /**
+ * @param int $fileClass
+ */
+ public function setFileClass($fileClass)
+ {
+ $this->fileClass = $fileClass;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFileClass()
+ {
+ return $this->fileClass;
+ }
+}
diff --git a/lib/Analyzer/FileNameAnalyzer.php b/lib/Analyzer/FileNameAnalyzer.php
new file mode 100644
index 0000000..8123903
--- /dev/null
+++ b/lib/Analyzer/FileNameAnalyzer.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\FileSignatureList;
+use OCA\RansomwareDetection\Entropy\Entropy;
+use OCP\ILogger;
+
+class FileNameAnalyzer
+{
+ /**
+ * File name entropy cut-off point between normal and suspicious.
+ *
+ * @var float
+ */
+ const ENTROPY_CUT_OFF = 4.0;
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var Entropy */
+ private $entropy;
+
+ /**
+ * @param ILogger $logger
+ * @param Entropy $entropy
+ */
+ public function __construct(
+ ILogger $logger,
+ Entropy $entropy
+ ) {
+ $this->logger = $logger;
+ $this->entropy = $entropy;
+ }
+
+ /**
+ * Classifies a file name in NORMAL, SUSPICIOUS_FILE_NAME,
+ * SUSPICIOUS_FILE_EXTENSION or SUSPICIOUS, if the file name
+ * and file extension are suspicious.
+ *
+ * @param string $path
+ *
+ * @return FileNameResult
+ */
+ public function analyze($path)
+ {
+ $fileName = $this->getFileName($path);
+ $extension = $this->getFileExtension($path);
+ $class = FileNameResult::NORMAL;
+
+ $isFileExtensionKnown = $this->isFileExtensionKnown($extension);
+ if (!$isFileExtensionKnown) {
+ $class += FileNameResult::SUSPICIOUS_FILE_EXTENSION;
+ }
+ $entropyOfFileName = $this->calculateEntropyOfFileName($fileName);
+ if ($entropyOfFileName > self::ENTROPY_CUT_OFF) {
+ $class += FileNameResult::SUSPICIOUS_FILE_NAME;
+ }
+
+ return new FileNameResult($class, $isFileExtensionKnown, $entropyOfFileName);
+ }
+
+ /**
+ * Checks if the file extension is known.
+ *
+ * @param string $extension
+ *
+ * @return bool
+ */
+ private function isFileExtensionKnown($extension)
+ {
+ $signatures = FileSignatureList::getSignatures();
+ foreach ($signatures as $signature) {
+ if (in_array(strtolower($extension), $signature['extension'])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the file name of a path.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ private function getFileName($path)
+ {
+ $file = pathinfo($path);
+
+ return $file['basename'];
+ }
+
+ /**
+ * Returns the file extension of a file name.
+ *
+ * @param string $fileName
+ *
+ * @return string
+ */
+ private function getFileExtension($fileName)
+ {
+ $file = pathinfo($fileName);
+
+ return $file['extension'];
+ }
+
+ /**
+ * Calculates the entropy of the a file name.
+ *
+ * @param string $fileName
+ *
+ * @return float
+ */
+ private function calculateEntropyOfFileName($fileName)
+ {
+ return $this->entropy->calculateEntropy($fileName);
+ }
+}
diff --git a/lib/Analyzer/FileNameResult.php b/lib/Analyzer/FileNameResult.php
new file mode 100644
index 0000000..10d4f82
--- /dev/null
+++ b/lib/Analyzer/FileNameResult.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+class FileNameResult
+{
+ /**
+ * File name classes.
+ *
+ * @var int
+ */
+ const NORMAL = 0;
+ const SUSPICIOUS_FILE_EXTENSION = 1;
+ const SUSPICIOUS_FILE_NAME = 2;
+ const SUSPICIOUS = 3;
+
+ /** @var int */
+ private $fileNameClass;
+
+ /** @var bool */
+ private $isFileExtensionKnown;
+
+ /** @var float */
+ private $entropyOfFileName;
+
+ /**
+ * @param int $fileNameClass
+ * @param bool $isFileExtensionKnown
+ * @param float $entropyOfFileName
+ */
+ public function __construct(
+ $fileNameClass,
+ $isFileExtensionKnown,
+ $entropyOfFileName
+ ) {
+ $this->fileNameClass = $fileNameClass;
+ $this->isFileExtensionKnown = $isFileExtensionKnown;
+ $this->entropyOfFileName = $entropyOfFileName;
+ }
+
+ /**
+ * @param int $fileNameClass
+ */
+ public function setFileNameClass($fileNameClass)
+ {
+ $this->fileNameClass = $fileNameClass;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFileNameClass()
+ {
+ return $this->fileNameClass;
+ }
+
+ /**
+ * @param bool $isFileExtensionKnown
+ */
+ public function setFileExtensionKnown($isFileExtensionKnown)
+ {
+ $this->isFileExtensionKnown = $isFileExtensionKnown;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFileExtensionKnown()
+ {
+ return $this->isFileExtensionKnown;
+ }
+
+ /**
+ * @param float $entropyOfFileName
+ */
+ public function setEntropyOfFileName($entropyOfFileName)
+ {
+ $this->entropyOfFileName = $entropyOfFileName;
+ }
+
+ /**
+ * @return float
+ */
+ public function getEntropyOfFileName()
+ {
+ return $this->entropyOfFileName;
+ }
+}
diff --git a/lib/Analyzer/FileTypeFunnellingAnalyzer.php b/lib/Analyzer/FileTypeFunnellingAnalyzer.php
new file mode 100644
index 0000000..ad33e8b
--- /dev/null
+++ b/lib/Analyzer/FileTypeFunnellingAnalyzer.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Monitor;
+
+class FileTypeFunnellingAnalyzer
+{
+ /**
+ * Analyzes if the written files fulfill the property of
+ * file type funneling.
+ *
+ * Therefor classifies the sequence in three funneling classes:
+ *
+ * Class 1: All file extensions are unknown and the same.
+ * Class 2: All file extensions are unknown and every extensions is distinct.
+ * Class 3: All file extensions are unknown.
+ * Class 4: All file extensions are known, but all files are corrupted.
+ *
+ * @param int $sequence
+ *
+ * @return int Class of file type funneling
+ */
+ public function analyze($sequence)
+ {
+ $writtenExtensions = [];
+ $deletedExtensions = [];
+ $corruptedFiles = [];
+ $writtenFiles = [];
+ $numberOfKnownFileExtensions = 0;
+ $numberOfInfoFiles = 0;
+
+ foreach ($sequence as $file) {
+ if ($file->getType() === 'file') {
+ switch ($file->getCommand()) {
+ case Monitor::WRITE:
+ if ($file->getSuspicionClass() === Classifier::NOT_SUSPICIOUS) {
+ if ($numberOfInfoFiles < SequenceAnalyzer::NUMBER_OF_INFO_FILES) {
+ $numberOfInfoFiles++;
+ break;
+ }
+ }
+ $numberOfKnownFileExtensions += $this->countKnownFileExtensions($file);
+ $pathInfo = pathinfo($file->getOriginalName());
+ $writtenExtensions[$pathInfo['extension']] = 1;
+ $writtenFiles[] = $file;
+ if ($file->getCorrupted()) {
+ $corruptedFiles[] = $file;
+ }
+ break;
+ case Monitor::READ:
+ break;
+ case Monitor::RENAME:
+ break;
+ case Monitor::DELETE:
+ $pathInfo = pathinfo($file->getOriginalName());
+ $deletedExtensions[] = $pathInfo['extension'];
+ break;
+ case Monitor::CREATE:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // File type funneling must be at least 2 files
+ if (sizeof($writtenFiles) > 2) {
+ // Some files were written
+ if ($numberOfKnownFileExtensions === 0) {
+ if (sizeof($writtenExtensions) === sizeof($writtenFiles)) {
+ // All files have distinct unknown extensions
+ return 2;
+ }
+ if (sizeof($writtenExtensions) === 1) {
+ // All files have the same extension
+ return 2;
+ }
+ // All file extensions are unknown
+ return 1;
+ } elseif ($numberOfKnownFileExtensions === sizeof($writtenFiles)) {
+ if ($numberOfKnownFileExtensions === sizeof($corruptedFiles)) {
+ // All files are corrupted
+ return 2;
+ }
+ // All written files have known extensions
+ return 0;
+ }
+ // Some files are known
+ return 0;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Count the known file extensions.
+ *
+ * @param Entity $file
+ */
+ private function countKnownFileExtensions($file)
+ {
+ if (intval($file->getFileNameClass()) === FileNameResult::NORMAL || intval($file->getFileNameClass()) === FileNameResult::SUSPICIOUS_FILE_NAME) {
+ return 1;
+ }
+ }
+}
diff --git a/lib/Analyzer/SequenceAnalyzer.php b/lib/Analyzer/SequenceAnalyzer.php
new file mode 100644
index 0000000..35b7c29
--- /dev/null
+++ b/lib/Analyzer/SequenceAnalyzer.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Classifier;
+
+class SequenceAnalyzer
+{
+ /**
+ * Number of information files.
+ *
+ * @var int
+ */
+ const NUMBER_OF_INFO_FILES = 10;
+
+ /** @var SequenceSizeAnalyzer */
+ private $sequenceSizeAnalyzer;
+
+ /** @var FileTypeFunnellingAnalyzer */
+ private $fileTypeFunnellingAnalyzer;
+
+ /** @var EntropyFunnellingAnalyzer */
+ private $entropyFunnellingAnalyzer;
+
+ /**
+ * SequenceAnalyzer constructor.
+ *
+ * @param SequenceSizeAnalyzer $sequenceSizeAnalyzer
+ * @param FileTypeFunnellingAnalyzer $fileTypeFunnellingAnalyzer
+ * @param EntropyFunnellingAnalyzer $entropyFunnellingAnalyzer
+ */
+ public function __construct(
+ SequenceSizeAnalyzer $sequenceSizeAnalyzer,
+ FileTypeFunnellingAnalyzer $fileTypeFunnellingAnalyzer,
+ EntropyFunnellingAnalyzer $entropyFunnellingAnalyzer
+ ) {
+ $this->sequenceSizeAnalyzer = $sequenceSizeAnalyzer;
+ $this->fileTypeFunnellingAnalyzer = $fileTypeFunnellingAnalyzer;
+ $this->entropyFunnellingAnalyzer = $entropyFunnellingAnalyzer;
+ }
+
+ /**
+ * The analysis of the sequence is seperated in three parts:
+ * The analysis: If the same number of files is deletedFilesd as written,
+ * with the special addition that the number of written files is in the
+ * range of [number of deletedFilesd files, number of deletedFilesd files + 4].
+ * To enhance this analysis the sum of the size of the files deletedFilesd and
+ * the sum of the size of the written files is compared.
+ *
+ * The next part is the analysis of the suspicion levels of the files written.
+ * Therefor the suspicions levels are weighted:
+ * High - 1
+ * Middle - 0.75
+ * Low - 0.5
+ * None - 0.25
+ *
+ * summed up and divided by the sum of all written files. The higher the result,
+ * the higher is the suspicion of the hole sequence.
+ *
+ * The last part is the file type funneling analysis.
+ *
+ * @param int $sequenceId
+ * @param array $sequence
+ *
+ * @return int The level of suspicion, if a sequence is malicious or not.
+ */
+ public function analyze($sequenceId, $sequence)
+ {
+ $sequenceResult = new SequenceResult($sequenceId, 0, 0, 0, 0, $sequence);
+ if (sizeof($sequence) === 0) {
+ return $sequenceResult;
+ }
+
+ $highSuspicionFiles = [];
+ $middleSuspicionFiles = [];
+ $lowSuspicionFiles = [];
+ $noSuspicionFiles = [];
+ $writtenFiles = [];
+ $sizeOfWrittenFiles = 0;
+ $deletedFiles = [];
+ $sizeOfDeletedFiles = 0;
+ $suspicionScore = 0;
+
+ foreach ($sequence as $file) {
+ if ($file->getType() === 'file') {
+ switch ($file->getCommand()) {
+ case Monitor::WRITE:
+ $writtenFiles[] = $file;
+ $sizeOfWrittenFiles = $sizeOfWrittenFiles + $file->getSize();
+ break;
+ case Monitor::READ:
+ break;
+ case Monitor::RENAME:
+ break;
+ case Monitor::DELETE:
+ $deletedFiles[] = $file;
+ $sizeOfDeletedFiles = $sizeOfDeletedFiles + $file->getSize();
+ break;
+ case Monitor::CREATE:
+ break;
+ default:
+ break;
+ }
+ switch ($file->getSuspicionClass()) {
+ case Classifier::HIGH_LEVEL_OF_SUSPICION:
+ $highSuspicionFiles[] = $file;
+ break;
+ case Classifier::MIDDLE_LEVEL_OF_SUSPICION:
+ $middleSuspicionFiles[] = $file;
+ break;
+ case Classifier::LOW_LEVEL_OF_SUSPICION:
+ $lowSuspicionFiles[] = $file;
+ break;
+ case Classifier::NOT_SUSPICIOUS:
+ $noSuspicionFiles[] = $file;
+ break;
+ case Classifier::NO_INFORMATION:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // compare files written and files deleted
+ if (sizeof($writtenFiles) > 0 && sizeof($deletedFiles) > 0) {
+ $sequenceResult->setSizeWritten($sizeOfWrittenFiles);
+ $sequenceResult->setSizeDeleted($sizeOfDeletedFiles);
+ $upperBound = sizeof($deletedFiles) + self::NUMBER_OF_INFO_FILES;
+ if (sizeof($writtenFiles) <= $upperBound && sizeof($writtenFiles) >= sizeof($deletedFiles)) {
+ if ($this->sequenceSizeAnalyzer->analyze($sequence) == SequenceSizeAnalyzer::EQUAL_SIZE) {
+ $sequenceResult->setQuantities(2);
+ $suspicionScore += 2;
+ } else {
+ $sequenceResult->setQuantities(1);
+ $suspicionScore += 1;
+ }
+ }
+ }
+
+ $numberOfWrittenFiles = sizeof($highSuspicionFiles) + sizeof($middleSuspicionFiles)
+ + sizeof($lowSuspicionFiles) + sizeof($noSuspicionFiles);
+
+ // remove info files from the weight
+ $numberOfInfoFiles = self::NUMBER_OF_INFO_FILES;
+ if (sizeof($noSuspicionFiles) < self::NUMBER_OF_INFO_FILES) {
+ $numberOfInfoFiles = sizeof($noSuspicionFiles);
+ }
+
+ // weight the suspicion levels.
+ $suspicionSum = (sizeof($highSuspicionFiles) * 1) + (sizeof($middleSuspicionFiles) * 0.75)
+ + (sizeof($lowSuspicionFiles) * 0.5) + ((sizeof($noSuspicionFiles) - $numberOfInfoFiles) * 0.25);
+
+ // check for division by zero.
+ if (($numberOfWrittenFiles - $numberOfInfoFiles) > 0) {
+ $sequenceResult->setFileSuspicion($suspicionSum / ($numberOfWrittenFiles - $numberOfInfoFiles));
+ $suspicionScore += $suspicionSum / ($numberOfWrittenFiles - $numberOfInfoFiles);
+ }
+
+ // entropy funnelling
+ $entropyFunnelling = $this->entropyFunnellingAnalyzer->analyze($deletedFiles, $writtenFiles);
+ $sequenceResult->setEntropyFunnelling($entropyFunnelling);
+ $suspicionScore += $entropyFunnelling->getEntropyFunnelling();
+
+ // check for file type funneling
+ $fileTypeFunnelling = $this->fileTypeFunnellingAnalyzer->analyze($sequence);
+ $sequenceResult->setFileTypeFunnelling($fileTypeFunnelling);
+ $suspicionScore += $fileTypeFunnelling;
+
+ $sequenceResult->setSuspicionScore($suspicionScore);
+
+ return $sequenceResult;
+ }
+}
diff --git a/lib/Analyzer/SequenceOperationsAnalyzer.php b/lib/Analyzer/SequenceOperationsAnalyzer.php
new file mode 100644
index 0000000..a515e91
--- /dev/null
+++ b/lib/Analyzer/SequenceOperationsAnalyzer.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+
+class SequenceOperationsAnalyzer
+{
+ /**
+ * Sequence operation class.
+ *
+ * @var int
+ */
+ const NO_WRITE_AND_DELETE = 0;
+ const ONLY_WRITE = 1;
+ const ONLY_DELETE = 2;
+ const EQUAL_WRITE_AND_DELETE = 3;
+ const DIFF_WRITE_AND_DELETE = 4;
+
+ /**
+ * Classifies the operations in a sequence.
+ *
+ * @param array $sequence
+ *
+ * @return int
+ */
+ public function analyze($sequence)
+ {
+ $numberOfWrittenFiles = 0;
+ $numberOfDeletedFiles = 0;
+ $numberOfRenamedFiles = 0;
+
+ $sequenceClass = self::NO_WRITE_AND_DELETE;
+
+ foreach ($sequence as $fileOperation) {
+ switch ($fileOperation->getCommand()) {
+ case Monitor::WRITE:
+ $numberOfWrittenFiles++;
+ break;
+ case Monitor::READ:
+ break;
+ case Monitor::RENAME:
+ $numberOfRenamedFiles++;
+ break;
+ case Monitor::DELETE:
+ $numberOfDeletedFiles++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ($numberOfWrittenFiles > 0) {
+ if ($numberOfDeletedFiles > 0) {
+ if ($numberOfWrittenFiles === $numberOfDeletedFiles) {
+ $sequenceClass = self::EQUAL_WRITE_AND_DELETE;
+ } else {
+ $sequenceClass = self::DIFF_WRITE_AND_DELETE;
+ }
+ } else {
+ $sequenceClass = self::ONLY_WRITE;
+ }
+ } else {
+ if ($numberOfDeletedFiles > 0) {
+ $sequenceClass = self::ONLY_DELETE;
+ }
+ }
+
+ return $sequenceClass;
+ }
+}
diff --git a/lib/Analyzer/SequenceResult.php b/lib/Analyzer/SequenceResult.php
new file mode 100644
index 0000000..b5fce8b
--- /dev/null
+++ b/lib/Analyzer/SequenceResult.php
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+class SequenceResult
+{
+ /** @var int */
+ protected $sequenceId;
+
+ /** @var float */
+ protected $fileSuspicion;
+
+ /** @var float */
+ protected $quantities;
+
+ /** @var int */
+ protected $fileTypeFunnelling;
+
+ /** @var int */
+ protected $entropyFunnelling;
+
+ /** @var float */
+ protected $suspicionScore;
+
+ /** @var int */
+ protected $sizeWritten;
+
+ /** @var int */
+ protected $sizeDeleted;
+
+ /** @var array */
+ protected $sequence;
+
+ /**
+ * @param int $sequenceId
+ * @param float $fileSuspicion
+ * @param float $quantities
+ * @param float $fileTypeFunnelling
+ * @param float $suspicionScore
+ * @param array $sequence
+ */
+ public function __construct(
+ $sequenceId,
+ $fileSuspicion,
+ $quantities,
+ $fileTypeFunnelling,
+ $suspicionScore,
+ $sequence
+ ) {
+ $this->sequenceId = $sequenceId;
+ $this->fileSuspicion = $fileSuspicion;
+ $this->quantities = $quantities;
+ $this->fileTypeFunnelling = $fileTypeFunnelling;
+ $this->suspicionScore = $suspicionScore;
+ $this->sequence = $sequence;
+ }
+
+ public function getSequenceId()
+ {
+ return $this->sequenceId;
+ }
+
+ public function setSequenceId($sequenceId)
+ {
+ $this->sequenceId = $sequenceId;
+ }
+
+ public function getFileSuspicion()
+ {
+ return $this->fileSuspicion;
+ }
+
+ public function setFileSuspicion($fileSuspicion)
+ {
+ $this->fileSuspicion = $fileSuspicion;
+ }
+
+ public function getQuantities()
+ {
+ return $this->quantities;
+ }
+
+ public function setQuantities($quantities)
+ {
+ $this->quantities = $quantities;
+ }
+
+ public function getFileTypeFunnelling()
+ {
+ return $this->fileTypeFunnelling;
+ }
+
+ public function setFileTypeFunnelling($fileTypeFunnelling)
+ {
+ $this->fileTypeFunnelling = $fileTypeFunnelling;
+ }
+
+ public function getEntropyFunnelling()
+ {
+ return $this->entropyFunnelling;
+ }
+
+ public function setEntropyFunnelling($entropyFunnelling)
+ {
+ $this->entropyFunnelling = $entropyFunnelling;
+ }
+
+ public function getSuspicionScore()
+ {
+ return $this->suspicionScore;
+ }
+
+ public function setSuspicionScore($suspicionScore)
+ {
+ $this->suspicionScore = $suspicionScore;
+ }
+
+ public function setSizeWritten($sizeWritten)
+ {
+ $this->sizeWritten = $sizeWritten;
+ }
+
+ public function getSizeWritten()
+ {
+ return $this->sizeWritten;
+ }
+
+ public function setSizeDeleted($sizeDeleted)
+ {
+ $this->sizeDeleted = $sizeDeleted;
+ }
+
+ public function getSizeDeleted()
+ {
+ return $this->sizeDeleted;
+ }
+
+ public function getSequence()
+ {
+ return $sequence;
+ }
+
+ public function setSequence($sequence)
+ {
+ $this->sequence = $sequence;
+ }
+
+ public function toArray()
+ {
+ $var = get_object_vars($this);
+ foreach ($var as &$value) {
+ if (is_object($value) && method_exists($value, 'toArray')) {
+ $value = $value->toArray();
+ }
+ }
+
+ return $var;
+ }
+}
diff --git a/lib/Analyzer/SequenceSizeAnalyzer.php b/lib/Analyzer/SequenceSizeAnalyzer.php
new file mode 100644
index 0000000..4983c37
--- /dev/null
+++ b/lib/Analyzer/SequenceSizeAnalyzer.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+
+class SequenceSizeAnalyzer
+{
+ /**
+ * Size of information files.
+ *
+ * @var int
+ */
+ const SIZE_OF_INFO_FILES = 5000000;
+
+ /**
+ * Sequence size class.
+ *
+ * @var int
+ */
+ const EQUAL_SIZE = 1;
+ const DIFF_SIZE = 2;
+
+ /**
+ * Compares the sum of the size of the files written and deleted.
+ *
+ * @param array $sequence
+ *
+ * @return int
+ */
+ public function analyze($sequence)
+ {
+ $sizeOfWrittenFiles = 0;
+ $sizeOfDeletedFiles = 0;
+
+ foreach ($sequence as $file) {
+ switch ($file->getCommand()) {
+ case Monitor::WRITE:
+ $sizeOfWrittenFiles = $sizeOfWrittenFiles + $file->getSize();
+ break;
+ case Monitor::READ:
+ break;
+ case Monitor::RENAME:
+ break;
+ case Monitor::DELETE:
+ $sizeOfDeletedFiles = $sizeOfDeletedFiles + $file->getSize();
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ($sizeOfWrittenFiles <= ($sizeOfDeletedFiles + self::SIZE_OF_INFO_FILES) && $sizeOfWrittenFiles >= $sizeOfDeletedFiles) {
+ return self::EQUAL_SIZE;
+ } else {
+ return self::DIFF_SIZE;
+ }
+ }
+}
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
new file mode 100644
index 0000000..d0c81fe
--- /dev/null
+++ b/lib/AppInfo/Application.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\AppInfo;
+
+use OC\Files\Filesystem;
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\Analyzer\SequenceSizeAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileTypeFunnellingAnalyzer;
+use OCA\RansomwareDetection\Analyzer\EntropyFunnellingAnalyzer;
+use OCA\RansomwareDetection\Notification\Notifier;
+use OCA\RansomwareDetection\StorageWrapper;
+use OCA\RansomwareDetection\Connector\Sabre\RequestPlugin;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCA\RansomwareDetection\Mapper\FileOperationMapper;
+use OCP\AppFramework\App;
+use OCP\Files\Storage\IStorage;
+use OCP\Notification\IManager;
+use OCP\Util;
+use OCP\SabrePluginEvent;
+use OCP\ILogger;
+use OCP\IConfig;
+use OCP\IUserSession;
+use OCP\ISession;
+
+class Application extends App
+{
+ const APP_ID = 'ransomware_detection';
+
+ public function __construct()
+ {
+ parent::__construct(self::APP_ID);
+
+ $container = $this->getContainer();
+
+ // mapper
+ $container->registerService('FileOperationMapper', function ($c) {
+ return new FileOperationMapper(
+ $c->query('ServerContainer')->getDb()
+ );
+ });
+
+ // services
+ $container->registerService('FileOperationService', function ($c) {
+ return new FileOperationService(
+ $c->query('FileOperationMapper'),
+ $c->query('ServerContainer')->getUserSession()->getUser()->getUID()
+ );
+ });
+
+ // classifier
+ $container->registerService('Classifier', function ($c) {
+ return new Classifier(
+ $c->query(ILogger::class),
+ $c->query(FileOperationMapper::class),
+ $c->query(FileOperationService::class)
+ );
+ });
+
+ // analyzer
+ $container->registerService('SequenceSizeAnalyzer', function ($c) {
+ return new SequenceSizeAnalyzer();
+ });
+
+ $container->registerService('FileTypeFunnellingAnalyzer', function ($c) {
+ return new FileTypeFunnellingAnalyzer();
+ });
+
+ $container->registerService('EntropyFunnellingAnalyzer', function ($c) {
+ return new EntropyFunnellingAnalyzer(
+ $c->query(ILogger::class)
+ );
+ });
+
+ $container->registerService('SequenceAnalyzer', function ($c) {
+ return new SequenceAnalyzer(
+ $c->query(SequenceSizeAnalyzer::class),
+ $c->query(FileTypeFunnellingAnalyzer::class),
+ $c->query(EntropyFunnellingAnalyzer::class)
+ );
+ });
+ }
+
+ /**
+ * Register hooks.
+ */
+ public function register()
+ {
+ // register sabre plugin to catch the profind requests
+ $eventDispatcher = $this->getContainer()->getServer()->getEventDispatcher();
+ $eventDispatcher->addListener('OCA\DAV\Connector\Sabre::addPlugin', function (SabrePluginEvent $event) {
+ $logger = $this->getContainer()->query(ILogger::class);
+ $config = $this->getContainer()->query(IConfig::class);
+ $userSession = $this->getContainer()->query(IUserSession::class);
+ $session = $this->getContainer()->query(ISession::class);
+ $service = $this->getContainer()->query(FileOperationService::class);
+ $notifications = $this->getContainer()->query(IManager::class);
+ $classifier = $this->getContainer()->query(Classifier::class);
+ $sequenceAnalyzer = $this->getContainer()->query(SequenceAnalyzer::class);
+ $event->getServer()->addPlugin(new RequestPlugin($logger, $config, $userSession, $session, $service, $notifications, $classifier, $sequenceAnalyzer));
+ });
+ Util::connectHook('OC_Filesystem', 'preSetup', $this, 'addStorageWrapper');
+ $this->registerNotificationNotifier();
+ }
+
+ /**
+ * @internal
+ */
+ public function addStorageWrapper()
+ {
+ Filesystem::addStorageWrapper(self::APP_ID, [$this, 'addStorageWrapperCallback'], -10);
+ }
+
+ /**
+ * @internal
+ *
+ * @param string $mountPoint
+ * @param IStorage $storage
+ *
+ * @return StorageWrapper|IStorage
+ */
+ public function addStorageWrapperCallback($mountPoint, IStorage $storage)
+ {
+ if (!\OC::$CLI && !$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
+ /** @var Monitor $monitor */
+ $monitor = $this->getContainer()->query(Monitor::class);
+
+ return new StorageWrapper([
+ 'storage' => $storage,
+ 'mountPoint' => $mountPoint,
+ 'monitor' => $monitor,
+ ]);
+ }
+
+ return $storage;
+ }
+
+ protected function registerNotificationNotifier()
+ {
+ $this->getContainer()->getServer()->getNotificationManager()->registerNotifier(function () {
+ return $this->getContainer()->query(Notifier::class);
+ }, function () {
+ $l = $this->getContainer()->getServer()->getL10NFactory()->get(self::APP_ID);
+
+ return [
+ 'id' => self::APP_ID,
+ 'name' => $l->t('Ransomware detection'),
+ ];
+ });
+ }
+}
diff --git a/lib/BackgroundJob/CleanUpJob.php b/lib/BackgroundJob/CleanUpJob.php
new file mode 100644
index 0000000..6e7a5a3
--- /dev/null
+++ b/lib/BackgroundJob/CleanUpJob.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\BackgroundJob;
+
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OC\BackgroundJob\TimedJob;
+use OCP\IConfig;
+
+class CleanUpJob extends TimedJob
+{
+ /** @var IConfig */
+ protected $config;
+
+ /** @var FileOperationService */
+ protected $fileOperationService;
+
+ /**
+ * @param FileOperationService $fileOperationService
+ * @param IConfig $config
+ */
+ public function __construct(
+ FileOperationService $fileOperationService,
+ IConfig $config
+ ) {
+ // Run once a day
+ $this->setInterval(24 * 60 * 60);
+ $this->fileOperationService = $fileOperationService;
+ $this->config = $config;
+ }
+
+ public function run($argument)
+ {
+ $expireDays = $this->config->getAppValue('ransomware_detection', 'expire_days', 7);
+ $this->fileOperationService->deleteFileOperationsBefore(strtotime('-'.$expireDays.' day'));
+ }
+}
diff --git a/lib/CacheWrapper.php b/lib/CacheWrapper.php
new file mode 100644
index 0000000..f705a5b
--- /dev/null
+++ b/lib/CacheWrapper.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection;
+
+use OC\Files\Cache\Wrapper\CacheWrapper as Wrapper;
+use OCP\Constants;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Storage\IStorage;
+
+class CacheWrapper extends Wrapper
+{
+ /** @var Monitor */
+ protected $monitor;
+
+ /** @var StorageWrapper */
+ protected $storage;
+
+ /** @var int */
+ protected $mask;
+
+ /**
+ * @param ICache $cache
+ * @param IStorage $storage
+ * @param Monitor $monitor
+ */
+ public function __construct(
+ ICache $cache,
+ IStorage $storage,
+ Monitor $monitor
+ ) {
+ parent::__construct($cache);
+ $this->storage = $storage;
+ $this->monitor = $monitor;
+ $this->mask = Constants::PERMISSION_ALL;
+ $this->mask &= ~Constants::PERMISSION_READ;
+ $this->mask &= ~Constants::PERMISSION_CREATE;
+ $this->mask &= ~Constants::PERMISSION_UPDATE;
+ }
+
+ protected function formatCacheEntry($entry)
+ {
+ if (isset($entry['path'])) {
+ $this->monitor->analyze($this->storage, [$entry['path']], Monitor::READ);
+ }
+
+ return $entry;
+ }
+}
diff --git a/lib/Classifier.php b/lib/Classifier.php
new file mode 100644
index 0000000..3311d41
--- /dev/null
+++ b/lib/Classifier.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection;
+
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCP\ILogger;
+
+class Classifier
+{
+ /**
+ * File suspicion levels.
+ *
+ * @var int
+ */
+ const HIGH_LEVEL_OF_SUSPICION = 1;
+ const MIDDLE_LEVEL_OF_SUSPICION = 2;
+ const LOW_LEVEL_OF_SUSPICION = 3;
+ const NOT_SUSPICIOUS = 4;
+ const NO_INFORMATION = 5;
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var FileOperationMapper */
+ private $mapper;
+
+ /** @var FileOperationService */
+ private $service;
+
+ /**
+ * @param ILogger $logger
+ * @param FileOperationMapper $mapper
+ * @param FileOperationService $service
+ */
+ public function __construct(
+ ILogger $logger,
+ FileOperationMapper $mapper,
+ FileOperationService $service
+ ) {
+ $this->logger = $logger;
+ $this->mapper = $mapper;
+ $this->service = $service;
+ }
+
+ /**
+ * Classifies a file.
+ *
+ * @param Entity $file
+ *
+ * @return Entity Classified file.
+ */
+ public function classifyFile($file)
+ {
+ $file->setSuspicionClass(self::NO_INFORMATION);
+ if ($file->getCommand() == Monitor::WRITE ||
+ $file->getCommand() == Monitor::RENAME ||
+ $file->getCommand() == Monitor::DELETE ||
+ $file->getCommand() == Monitor::CREATE
+ ) {
+ if ($file->getFileClass() == EntropyResult::ENCRYPTED) {
+ if ($file->getFileNameClass() == FileNameResult::SUSPICIOUS) {
+ $file->setSuspicionClass(self::HIGH_LEVEL_OF_SUSPICION);
+ } elseif ($file->getFileNameClass() > FileNameResult::NORMAL) {
+ $file->setSuspicionClass(self::MIDDLE_LEVEL_OF_SUSPICION);
+ } else {
+ $file->setSuspicionClass(self::NOT_SUSPICIOUS);
+ }
+ } elseif ($file->getFileClass() == EntropyResult::COMPRESSED) {
+ if ($file->getFileNameClass() == FileNameResult::SUSPICIOUS) {
+ $file->setSuspicionClass(self::MIDDLE_LEVEL_OF_SUSPICION);
+ } elseif ($file->getFileNameClass() > FileNameResult::NORMAL) {
+ $file->setSuspicionClass(self::LOW_LEVEL_OF_SUSPICION);
+ } else {
+ $file->setSuspicionClass(self::NOT_SUSPICIOUS);
+ }
+ } else {
+ $file->setSuspicionClass(self::NOT_SUSPICIOUS);
+ }
+ }
+
+ return $file;
+ }
+}
diff --git a/lib/Connector/Sabre/RequestPlugin.php b/lib/Connector/Sabre/RequestPlugin.php
new file mode 100644
index 0000000..e4cc918
--- /dev/null
+++ b/lib/Connector/Sabre/RequestPlugin.php
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Connector\Sabre;
+
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCP\Notification\IManager;
+use OCP\IUserSession;
+use OCP\ISession;
+use OCP\ILogger;
+use OCP\IConfig;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+class RequestPlugin extends ServerPlugin
+{
+ /** @var Server */
+ protected $server;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IConfig */
+ protected $config;
+
+ /** @var IUserSession */
+ protected $userSession;
+
+ /** @var ISession */
+ protected $session;
+
+ /** @var FileOperationService */
+ protected $service;
+
+ /** @var IManager */
+ protected $notifications;
+
+ /** @var Classifier */
+ protected $classifier;
+
+ /** @var SequenceAnalyzer */
+ protected $sequenceAnalyzer;
+
+ const PROPFIND_COUNT = 6;
+
+ /**
+ * @param ILogger $logger
+ * @param IConfig $config
+ * @param IUserSession $userSession
+ * @param ISession $session
+ * @param FileOperationService $service
+ * @param IManager $notifications
+ * @param Classifier $classifer
+ * @param SequenceAnalyzer $sequenceAnalyzer
+ */
+ public function __construct(
+ ILogger $logger,
+ IConfig $config,
+ IUserSession $userSession,
+ ISession $session,
+ FileOperationService $service,
+ IManager $notifications,
+ Classifier $classifier,
+ SequenceAnalyzer $sequenceAnalyzer
+ ) {
+ $this->logger = $logger;
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->session = $session;
+ $this->service = $service;
+ $this->notifications = $notifications;
+ $this->classifier = $classifier;
+ $this->sequenceAnalyzer = $sequenceAnalyzer;
+ }
+
+ public function initialize(Server $server)
+ {
+ $this->server = $server;
+ $server->on('method:PROPFIND', [$this, 'beforeHttpPropFind'], 100);
+ $server->on('method:PUT', [$this, 'beforeHttpPut'], 100);
+ $server->on('method:DELETE', [$this, 'beforeHttpDelete'], 100);
+ $server->on('method:GET', [$this, 'beforeHttpGet'], 100);
+ $server->on('method:POST', [$this, 'beforeHttpPost'], 100);
+ }
+
+ public function beforeHttpPropFind(RequestInterface $request, ResponseInterface $response)
+ {
+ $propfindCount = $this->config->getUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'propfind_count:'.$this->session->getId(), 0);
+ if ($propfindCount + 1 < self::PROPFIND_COUNT) {
+ // less than PROPFIND_COUNT + 1 PROPFIND requests
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'propfind_count:'.$this->session->getId(), $propfindCount + 1);
+ } else {
+ // more than PROPFIND_COUNT PROPFIND requests and no file is uploading
+ $sequenceId = $this->config->getUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'sequence_id', 0);
+ $sequence = $this->service->findSequenceById([$sequenceId]);
+ if (sizeof($sequence) > 0) {
+ // get old sequence id
+ // start a new sequence by increasing the sequence id
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'sequence_id', $sequenceId + 1);
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'propfind_count:'.$this->session->getId(), 0);
+ $this->classifySequence($sequence);
+ }
+ }
+ }
+
+ public function beforeHttpPut(RequestInterface $request, ResponseInterface $response)
+ {
+ // extend if necessary
+ }
+
+ public function beforeHttpDelete(RequestInterface $request, ResponseInterface $response)
+ {
+ // extend if necessary
+ }
+
+ public function beforeHttpGet(RequestInterface $request, ResponseInterface $response)
+ {
+ // extend if necessary
+ }
+
+ public function beforeHttpPost(RequestInterface $request, ResponseInterface $response)
+ {
+ // extend if necessary
+ }
+
+ /**
+ * Triggers a notification.
+ */
+ private function triggerNotification($notification)
+ {
+ $notification->setApp(Application::APP_ID);
+ $notification->setDateTime(new \DateTime());
+ $notification->setObject('ransomware', 'ransomware');
+ $notification->setSubject('ransomware_attack_detected', []);
+ $notification->setUser($this->userSession->getUser()->getUID());
+
+ $this->notifications->notify($notification);
+ }
+
+ /**
+ * Classify sequence and if suspicion level is reached
+ * trigger a notification.
+ *
+ * @param array $sequence
+ */
+ private function classifySequence($sequence)
+ {
+ $sequenceSuspicionLevel = $this->config->getAppValue(Application::APP_ID, 'suspicionLevel', 3);
+
+ foreach ($sequence as $file) {
+ $this->classifier->classifyFile($file);
+ }
+
+ // sequence suspicion level
+ if ($sequenceSuspicionLevel == 1) {
+ $level = 3;
+ } elseif ($sequenceSuspicionLevel == 2) {
+ $level = 5;
+ } elseif ($sequenceSuspicionLevel == 3) {
+ $level = 6;
+ }
+
+ // sequence id is irrelevant so we use 0
+ $sequenceResult = $this->sequenceAnalyzer->analyze(0, $sequence);
+ if ($sequenceResult->getSuspicionScore() >= $level) {
+ $this->triggerNotification($this->notifications->createNotification());
+ }
+ }
+}
diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
new file mode 100644
index 0000000..859472c
--- /dev/null
+++ b/lib/Controller/ApiController.php
@@ -0,0 +1,367 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Controller;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCA\Files_Trashbin\Trashbin;
+use OCA\Files_Trashbin\Helper;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\OCSController;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\IConfig;
+use OCP\IUserSession;
+use OCP\IRequest;
+use OCP\ILogger;
+
+class ApiController extends OCSController
+{
+ /** @var IConfig */
+ protected $config;
+
+ /** @var IUserSession */
+ protected $userSession;
+
+ /** @var Classifier */
+ protected $classifier;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var Folder */
+ protected $userFolder;
+
+ /** @var FileOperationService */
+ protected $service;
+
+ /** @var SequenceAnalyzer */
+ protected $sequenceAnalyzer;
+
+ /** @var string */
+ protected $userId;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IUserSession $userSession
+ * @param IConfig $config
+ * @param Classifier $classifier
+ * @param ILogger $logger
+ * @param Folder $userFolder
+ * @param FileOperationService $service
+ * @param SequenceAnalyzer $sequenceAnalyzer
+ * @param string $userId
+ */
+ public function __construct(
+ $appName,
+ IRequest $request,
+ IUserSession $userSession,
+ IConfig $config,
+ Classifier $classifier,
+ ILogger $logger,
+ Folder $userFolder,
+ FileOperationService $service,
+ SequenceAnalyzer $sequenceAnalyzer,
+ $userId
+ ) {
+ parent::__construct($appName, $request);
+
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->classifier = $classifier;
+ $this->userFolder = $userFolder;
+ $this->logger = $logger;
+ $this->service = $service;
+ $this->sequenceAnalyzer = $sequenceAnalyzer;
+ $this->userId = $userId;
+ }
+
+ /**
+ * Lists the classified files and sequences.
+ *
+ * @NoAdminRequired
+ *
+ * @return JSONResponse
+ */
+ public function listFileOperations()
+ {
+ $files = $this->service->findAll();
+
+ $sequences = [];
+
+ // Classify files and put together the sequences.
+ foreach ($files as $file) {
+ $this->classifier->classifyFile($file);
+ $sequences[$file->getSequence()][] = $file;
+ }
+
+ $result = [];
+
+ foreach ($sequences as $sequenceId => $sequence) {
+ if (sizeof($sequence) >= $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 0)) {
+ usort($sequence, function ($a, $b) {
+ return $b->getId() - $a->getId();
+ });
+ $sequenceResult = $this->sequenceAnalyzer->analyze($sequenceId, $sequence);
+ $sequenceInformation = ['id' => $sequenceId, 'suspicionScore' => $sequenceResult->getSuspicionScore(), 'sequence' => $sequence];
+ $result[] = $sequenceInformation;
+ }
+ }
+
+ usort($result, function ($a, $b) {
+ return $b['id'] - $a['id'];
+ });
+
+ return new JSONResponse($result, Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Exports classification and analysis data.
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @param int $sequence
+ *
+ * @return JSONResponse
+ */
+ public function export()
+ {
+ $files = $this->service->findAll();
+
+ $sequences = [];
+
+ // Classify files and put together the sequences.
+ foreach ($files as $file) {
+ $this->classifier->classifyFile($file);
+ $sequences[$file->getSequence()][] = $file;
+ }
+
+ $result = [];
+
+ foreach ($sequences as $sequenceId => $sequence) {
+ if (sizeof($sequence) >= $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 0)) {
+ $result[] = $this->sequenceAnalyzer->analyze($sequenceId, $sequence)->toArray();
+ }
+ }
+
+ return new JSONResponse($result, Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Deletes a sequence from the database.
+ *
+ * @NoAdminRequired
+ *
+ * @param int $sequence
+ *
+ * @return JSONResponse
+ */
+ public function deleteSequence($sequence)
+ {
+ $files = $this->service->deleteSequenceById($sequence);
+
+ return new JSONResponse(['status' => 'success'], Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Get debug mode.
+ *
+ * @NoAdminRequired
+ *
+ * @return JSONResponse
+ */
+ public function getDebugMode()
+ {
+ $debugMode = $this->config->getAppValue(Application::APP_ID, 'debug', 0);
+
+ return new JSONResponse(['status' => 'success', 'message' => 'Get debug mode.', 'debug_mode' => $debugMode], Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Get color mode.
+ *
+ * @NoAdminRequired
+ *
+ * @return JSONResponse
+ */
+ public function getColorMode()
+ {
+ $colorMode = $this->config->getUserValue($this->userId, Application::APP_ID, 'colorMode', 0);
+
+ return new JSONResponse(['status' => 'success', 'message' => 'Get color mode.', 'color_mode' => $colorMode], Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Changes color mode.
+ *
+ * @NoAdminRequired
+ *
+ * @param int $colorMode
+ *
+ * @return JSONResponse
+ */
+ public function changeColorMode($colorMode)
+ {
+ $this->config->setUserValue($this->userId, Application::APP_ID, 'colorMode', $colorMode);
+
+ return new JSONResponse(['status' => 'success', 'message' => 'Color mode changed.'], Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Recover files from trashbin or remove them from normal storage.
+ *
+ * @NoAdminRequired
+ *
+ * @param int $id
+ *
+ * @return JSONResponse
+ */
+ public function recover($id)
+ {
+ try {
+ $file = $this->service->find($id);
+ if ($file->getCommand() === Monitor::WRITE) {
+ // Recover new created files by deleting them
+ $filePath = $file->getPath().'/'.$file->getOriginalName();
+ if ($this->deleteFromStorage($filePath)) {
+ $this->service->deleteById($id);
+
+ return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK);
+ } else {
+ return new JSONResponse(['status' => 'error', 'message' => 'File cannot be deleted.'], Http::STATUS_BAD_REQUEST);
+ }
+ } elseif ($file->getCommand() === Monitor::DELETE) {
+ // Recover deleted files by restoring them from the trashbin
+ // It's not necessary to use the real path
+ $dir = '/';
+ $candidate = $this->findCandidateToRestore($dir, $file->getOriginalName());
+ if ($candidate !== null) {
+ $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime'];
+ if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) {
+ $this->service->deleteById($id);
+
+ return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK);
+ }
+
+ return new JSONResponse(['status' => 'success', 'path' => $path, 'name' => $candidate['name'], 'mtime' => $candidate['mtime']], Http::STATUS_BAD_REQUEST);
+ } else {
+ return new JSONResponse(['status' => 'error', 'message' => 'No candidate found.'], Http::STATUS_BAD_REQUEST);
+ }
+ } elseif ($file->getCommand() === Monitor::RENAME) {
+ $this->service->deleteById($id);
+
+ return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK);
+ } elseif ($file->getCommand() === Monitor::CREATE) {
+ // Recover new created files by deleting them
+ $filePath = $file->getPath().'/'.$file->getOriginalName();
+ if ($this->deleteFromStorage($filePath)) {
+ $this->service->deleteById($id);
+
+ return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK);
+ } else {
+ return new JSONResponse(['status' => 'error', 'message' => 'File cannot be deleted.'], Http::STATUS_BAD_REQUEST);
+ }
+ } else {
+ // All other commands need no recovery
+ $this->service->deleteById($id);
+
+ return new JSONResponse(['id' => $id], Http::STATUS_OK);
+ }
+ } catch (\OCP\AppFramework\Db\MultipleObjectsReturnedException $exception) {
+ // Found more than one with the same file name
+ $this->logger->debug('recover: Found more than one with the same file name.', array('app' => Application::APP_ID));
+
+ return new JSONResponse(['status' => 'error', 'message' => 'Found more than one with the same file name.'], Http::STATUS_BAD_REQUEST);
+ } catch (\OCP\AppFramework\Db\DoesNotExistException $exception) {
+ // Nothing found
+ $this->logger->debug('recover: Files does not exist.', array('app' => Application::APP_ID));
+
+ return new JSONResponse(['status' => 'error', 'message' => 'Files does not exist.'], Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ /**
+ * Deletes a file from the storage.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ private function deleteFromStorage($path)
+ {
+ try {
+ $node = $this->userFolder->get($path);
+ if ($node->isDeletable()) {
+ $node->delete();
+ } else {
+ return false;
+ }
+
+ return true;
+ } catch (\OCP\Files\NotFoundException $exception) {
+ // Nothing found
+ $this->logger->debug('deleteFromStorage: Not found exception.', array('app' => Application::APP_ID));
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds a candidate to restore if a file with the specific does not exist.
+ *
+ * @param string $dir
+ * @param string $fileName
+ *
+ * @return FileInfo
+ */
+ private function findCandidateToRestore($dir, $fileName)
+ {
+ $files = array();
+ $trashBinFiles = $this->getTrashFiles($dir);
+
+ foreach ($trashBinFiles as $trashBinFile) {
+ if (strcmp($trashBinFile['name'], $fileName) === 0) {
+ $files[] = $trashBinFile;
+ }
+ }
+
+ return array_pop($files);
+ }
+
+ /**
+ * Workaround for testing.
+ *
+ * @param string path
+ *
+ * @return array
+ */
+ private function getTrashFiles($dir)
+ {
+ return Helper::getTrashFiles($dir, $this->userId, 'mtime', false);
+ }
+}
diff --git a/lib/Controller/RecoverController.php b/lib/Controller/RecoverController.php
new file mode 100644
index 0000000..0307460
--- /dev/null
+++ b/lib/Controller/RecoverController.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Controller;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IRequest;
+
+class RecoverController extends Controller
+{
+ /** @var int */
+ private $userId;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param string $userId
+ */
+ public function __construct(
+ $appName,
+ IRequest $request,
+ $userId
+ ) {
+ parent::__construct($appName, $request);
+ $this->userId = $userId;
+ }
+
+ /**
+ * Index page.
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @return TemplateResponse
+ */
+ public function index()
+ {
+ return new TemplateResponse(Application::APP_ID, 'index');
+ }
+}
diff --git a/lib/Db/FileOperation.php b/lib/Db/FileOperation.php
new file mode 100644
index 0000000..ea9c40e
--- /dev/null
+++ b/lib/Db/FileOperation.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Db;
+
+use OCP\AppFramework\Db\Entity;
+
+class FileOperation extends Entity
+{
+ /** @var string */
+ public $userId;
+
+ /** @var string */
+ public $path;
+
+ /** @var string */
+ public $originalName;
+
+ /** @var string */
+ public $newName;
+
+ /** @var string */
+ public $type;
+
+ /** @var string */
+ public $mimeType;
+
+ /** @var int */
+ public $size;
+
+ /** @var int */
+ public $corrupted;
+
+ /** @var string */
+ public $timestamp;
+
+ /** @var int */
+ public $command;
+
+ /** @var int */
+ public $sequence;
+
+ /** @var float */
+ public $entropy;
+
+ /** @var float */
+ public $standardDeviation;
+
+ /** @var float */
+ public $fileNameEntropy;
+
+ /** @var string */
+ public $fileClass;
+
+ /** @var string */
+ public $fileNameClass;
+
+ /** @var int */
+ public $suspicionClass;
+
+ public function __construct()
+ {
+ // Add types in constructor
+ $this->addType('size', 'integer');
+ $this->addType('corrupted', 'integer');
+ $this->addType('command', 'integer');
+ $this->addType('sequence', 'integer');
+ $this->addType('entropy', 'float');
+ $this->addType('standardDeviation', 'float');
+ $this->addType('fileNameEntropy', 'float');
+ $this->addType('suspicionClass', 'integer');
+ }
+}
diff --git a/lib/Db/FileOperationMapper.php b/lib/Db/FileOperationMapper.php
new file mode 100644
index 0000000..1a672d4
--- /dev/null
+++ b/lib/Db/FileOperationMapper.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Db;
+
+use OCP\IDBConnection;
+use OCP\AppFramework\Db\Mapper;
+
+class FileOperationMapper extends Mapper
+{
+ /**
+ * @param IDBConnection $db
+ */
+ public function __construct(
+ IDBConnection $db
+ ) {
+ parent::__construct($db, 'ransomware_detection_file_operation');
+ }
+
+ /**
+ * Find one by id.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @param int $id
+ *
+ * @return Entity
+ */
+ public function find($id, $userId)
+ {
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ return $this->findEntity($sql, [$id, $userId]);
+ }
+
+ /**
+ * Find one by file name.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @param string $name
+ *
+ * @return Entity
+ */
+ public function findOneByFileName($name, $userId)
+ {
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ return $this->findEntity($sql, [$name, $userId]);
+ }
+
+ /**
+ * Find the one with the highest id.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @return Entity
+ */
+ public function findOneWithHighestId($userId)
+ {
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ return $this->findEntity($sql, [$userId]);
+ }
+
+ /**
+ * Find all.
+ *
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ public function findAll(array $params = [], $limit = null, $offset = null)
+ {
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?';
+
+ return $this->findEntities($sql, $params, $limit, $offset);
+ }
+
+ /**
+ * Find a sequence by its id.
+ *
+ * @param array $params
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ public function findSequenceById(array $params = [], $limit = null, $offset = null)
+ {
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+
+ return $this->findEntities($sql, $params, $limit, $offset);
+ }
+
+ /**
+ * Delete entity by id.
+ *
+ * @param int $id
+ */
+ public function deleteById($id, $userId)
+ {
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `id` = ? AND `user_id` = ?';
+ $stmt = $this->execute($sql, [$id, $userId]);
+ $stmt->closeCursor();
+ }
+
+ /**
+ * Deletes a sequence of file operations.
+ *
+ * @param int $sequence
+ */
+ public function deleteSequenceById($sequence, $userId)
+ {
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+ $stmt = $this->execute($sql, [$sequence, $userId]);
+ $stmt->closeCursor();
+ }
+
+ /**
+ * Delete all entries before $timestamp.
+ *
+ * @param int $timestamp
+ */
+ public function deleteFileOperationsBefore($timestamp)
+ {
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `timestamp` < ?';
+ $stmt = $this->execute($sql, [$timestamp]);
+ $stmt->closeCursor();
+ }
+}
diff --git a/lib/Entropy/Entropy.php b/lib/Entropy/Entropy.php
new file mode 100644
index 0000000..36fde3c
--- /dev/null
+++ b/lib/Entropy/Entropy.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Entropy;
+
+use OCP\ILogger;
+
+class Entropy
+{
+ /** @var ILogger */
+ private $logger;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(
+ ILogger $logger
+ ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Calculates the entropy of data.
+ *
+ * @param string $data
+ *
+ * @return float
+ */
+ public function calculateEntropy($data)
+ {
+ $entropy = 0;
+ $size = strlen($data);
+
+ foreach (count_chars($data, 1) as $value) {
+ $p = $value / $size;
+ $entropy -= $p * log($p) / log(2);
+ }
+
+ return $entropy;
+ }
+
+ /**
+ * Calculates the standard deviation.
+ *
+ * @param array $array
+ *
+ * @return float
+ */
+ public function sd($array)
+ {
+ if (is_array($array) && count($array) > 0) {
+ // square root of sum of squares devided by N-1
+ return sqrt(array_sum(array_map(
+ function ($x, $mean) {
+ return pow($x - $mean, 2);
+ },
+ $array,
+ array_fill(
+ 0,
+ count($array),
+ (array_sum($array) / count($array))
+ )
+ )) / (count($array) - 1));
+ }
+
+ return 0.0;
+ }
+}
diff --git a/lib/FileSignatureList.php b/lib/FileSignatureList.php
new file mode 100644
index 0000000..7973c2c
--- /dev/null
+++ b/lib/FileSignatureList.php
@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection;
+
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+
+class FileSignatureList
+{
+ /**
+ * Signature definition.
+ *
+ * @var array
+ */
+ private static $signatures = [
+ ['byteSequence' => '00001A00051004', 'offset' => '0', 'extension' => ['123'], 'mimeType' => [['extension' => '123', 'mime' => 'application/vnd.lotus-1-2-3']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D5A', 'offset' => '0', 'extension' => ['cpl', 'exe', 'dll'], 'mimeType' => [['extension' => 'cpl', 'mime' => 'application/cpl+xml'], ['extension' => 'exe', 'mime' => 'application/octet-stream'], ['extension' => 'dll', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'DCDC', 'offset' => '0', 'extension' => ['cpl'], 'mimeType' => [['extension' => 'cpl', 'mime' => 'application/cpl+xml']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B03040A000200', 'offset' => '0', 'extension' => ['epub'], 'mimeType' => [['extension' => 'epub', 'mime' => 'application/epub+zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0001000000', 'offset' => '0', 'extension' => ['ttf'], 'mimeType' => [['extension' => 'ttf', 'mime' => 'application/font-sfnt']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '1F8B08', 'offset' => '0', 'extension' => ['gz', 'tgz'], 'mimeType' => [['extension' => 'gz', 'mime' => 'application/gzip'], ['extension' => 'tgz', 'mime' => 'application/gzip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '28546869732066696C65206D75737420626520636F6E76657274656420776974682042696E48657820', 'offset' => '0', 'extension' => ['hqx'], 'mimeType' => [['extension' => 'hqx', 'mime' => 'application/mac-binhex40']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0D444F43', 'offset' => '0', 'extension' => ['doc'], 'mimeType' => [['extension' => 'doc', 'mime' => 'application/msword']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'CF11E0A1B11AE100', 'offset' => '0', 'extension' => ['doc'], 'mimeType' => [['extension' => 'doc', 'mime' => 'application/msword']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'D0CF11E0A1B11AE1', 'offset' => '0', 'extension' => ['doc', 'apr', 'xls', 'xla', 'ppt', 'pps', 'dot'], 'mimeType' => [['extension' => 'doc', 'mime' => 'application/msword'], ['extension' => 'apr', 'mime' => 'application/vnd.lotus-approach'], ['extension' => 'xls', 'mime' => 'application/vnd.ms-excel'], ['extension' => 'xla', 'mime' => 'application/vnd.ms-excel'], ['extension' => 'ppt', 'mime' => 'application/vnd.ms-powerpoint'], ['extension' => 'pps', 'mime' => 'application/vnd.ms-powerpoint'], ['extension' => 'dot', 'mime' => 'text/vnd.graphviz']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'DBA52D00', 'offset' => '0', 'extension' => ['doc'], 'mimeType' => [['extension' => 'doc', 'mime' => 'application/msword']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'ECA5C100', 'offset' => '512', 'extension' => ['doc'], 'mimeType' => [['extension' => 'doc', 'mime' => 'application/msword']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '060E2B34020501010D0102010102', 'offset' => '0', 'extension' => ['mxf'], 'mimeType' => [['extension' => 'mxf', 'mime' => 'application/mxf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3C435472616E7354696D656C696E653E', 'offset' => '0', 'extension' => ['mxf'], 'mimeType' => [['extension' => 'mxf', 'mime' => 'application/mxf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2D6C68', 'offset' => '2', 'extension' => ['lha', 'lzh'], 'mimeType' => [['extension' => 'lha', 'mime' => 'application/octet-stream'], ['extension' => 'lzh', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'CAFEBABE', 'offset' => '0', 'extension' => ['class'], 'mimeType' => [['extension' => 'class', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000100005374616E64617264204A6574204442', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504943540008', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '514649FB', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '53434D49', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '7E742C015070024D52010000000800000001000031000000310000004301FF0001000800010000007e742c01', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'EB3C902A', 'offset' => '0', 'extension' => ['img'], 'mimeType' => [['extension' => 'img', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4344303031', 'offset' => '0', 'extension' => ['iso'], 'mimeType' => [['extension' => 'iso', 'mime' => 'application/octet-stream']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4F67675300020000000000000000', 'offset' => '0', 'extension' => ['ogx', 'oga', 'ogg', 'ogv'], 'mimeType' => [['extension' => 'ogx', 'mime' => 'application/ogg'], ['extension' => 'oga', 'mime' => 'audio/ogg'], ['extension' => 'ogg', 'mime' => 'audio/ogg'], ['extension' => 'ogv', 'mime' => 'video/ogg']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B0304', 'offset' => '0', 'extension' => ['oxps', 'kmz', 'kwd', 'xps', 'odp', 'odt', 'ott', 'zip', 'sxc', 'sxd', 'sxi', 'sxw', 'jar', 'xpi'], 'mimeType' => [['extension' => 'oxps', 'mime' => 'application/oxps'], ['extension' => 'kmz', 'mime' => 'application/vnd.google-earth.kmz'], ['extension' => 'kwd', 'mime' => 'application/vnd.kde.kword'], ['extension' => 'xps', 'mime' => 'application/vnd.ms-xpsdocument'], ['extension' => 'odp', 'mime' => 'application/vnd.oasis.opendocument.presentation'], ['extension' => 'odt', 'mime' => 'application/vnd.oasis.opendocument.text'], ['extension' => 'ott', 'mime' => 'application/vnd.oasis.opendocument.text-template'], ['extension' => 'zip', 'mime' => 'application/zip'], ['extension' => 'sxc', 'mime' => 'application/vnd.sun.xml.calc'], ['extension' => 'sxd', 'mime' => 'application/vnd.sun.xml.draw'], ['extension' => 'sxi', 'mime' => 'application/vnd.sun.xml.impress'], ['extension' => 'sxw', 'mime' => 'application/vnd.sun.xml.writer'], ['extension' => 'jar', 'mime' => 'application/x-java-archive'], ['extension' => 'xpi', 'mime' => 'application/x-xpinstall']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '25504446', 'offset' => '0', 'extension' => ['pdf', 'ai', 'fdf'], 'mimeType' => [['extension' => 'pdf', 'mime' => 'application/pdf'], ['extension' => 'ai', 'mime' => 'application/postscript'], ['extension' => 'fdf', 'mime' => 'application/vnd.fdf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '64000000', 'offset' => '0', 'extension' => ['p10'], 'mimeType' => [['extension' => 'p10', 'mime' => 'application/pkcs10']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5B706C61796C6973745D', 'offset' => '0', 'extension' => ['pls'], 'mimeType' => [['extension' => 'pls', 'mime' => 'application/pls+xml']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '252150532D41646F62652D332E3020455053462D332030', 'offset' => '0', 'extension' => ['eps'], 'mimeType' => [['extension' => 'eps', 'mime' => 'application/postscript']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'C5D0D3C6', 'offset' => '0', 'extension' => ['eps'], 'mimeType' => [['extension' => 'eps', 'mime' => 'application/postscript']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '7B5C72746631', 'offset' => '0', 'extension' => ['rtf'], 'mimeType' => [['extension' => 'rtf', 'mime' => 'application/rtf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '47', 'offset' => '0', 'extension' => ['tsa', 'tsv', 'ts'], 'mimeType' => [['extension' => 'tsa', 'mime' => 'application/tamp-sequence-adjust'], ['extension' => 'tsv', 'mime' => 'text/tab-separated-values'], ['extension' => 'ts', 'mime' => 'text/vnd.trolltech.linguist']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2F2F203C212D2D203C6D64623A6D6F726B3A7A', 'offset' => '0', 'extension' => ['msf'], 'mimeType' => [['extension' => 'msf', 'mime' => 'application/vnd.epson.msf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3C4D616B657246696C6520', 'offset' => '0', 'extension' => ['fm', 'mif'], 'mimeType' => [['extension' => 'fm', 'mime' => 'application/vnd.framemaker'], ['extension' => 'mif', 'mime' => 'application/vnd.mif']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0020AF30', 'offset' => '0', 'extension' => ['tpl'], 'mimeType' => [['extension' => 'tpl', 'mime' => 'application/vnd.groove-tool-template']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6D7346696C7465724C697374', 'offset' => '0', 'extension' => ['tpl'], 'mimeType' => [['extension' => 'tpl', 'mime' => 'application/vnd.groove-tool-template']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00001A000210040000000000', 'offset' => '0', 'extension' => ['wk4'], 'mimeType' => [['extension' => 'wk4', 'mime' => 'application/vnd.lotus-1-2-3']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00001A000010040000000000', 'offset' => '0', 'extension' => ['wk3'], 'mimeType' => [['extension' => 'wk3', 'mime' => 'application/vnd.lotus-1-2-3']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0000020006040600080000000000', 'offset' => '0', 'extension' => ['wk1'], 'mimeType' => [['extension' => 'wk1', 'mime' => 'application/vnd.lotus-1-2-3']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '1A0000040000', 'offset' => '0', 'extension' => ['nsf'], 'mimeType' => [['extension' => 'nsf', 'mime' => 'application/vnd.lotus-notes']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4E45534D1A01', 'offset' => '0', 'extension' => ['nsf'], 'mimeType' => [['extension' => 'nsf', 'mime' => 'application/vnd.lotus-notes']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '1A0000', 'offset' => '0', 'extension' => ['ntf'], 'mimeType' => [['extension' => 'ntf', 'mime' => 'application/vnd.lotus-notes']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '30314F52444E414E43452053555256455920202020202020', 'offset' => '0', 'extension' => ['ntf'], 'mimeType' => [['extension' => 'ntf', 'mime' => 'application/vnd.lotus-notes']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4E49544630', 'offset' => '0', 'extension' => ['ntf'], 'mimeType' => [['extension' => 'ntf', 'mime' => 'application/vnd.lotus-notes']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '414F4C564D313030', 'offset' => '0', 'extension' => ['org'], 'mimeType' => [['extension' => 'org', 'mime' => 'application/vnd.lotus-organizer']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '576F726450726F', 'offset' => '0', 'extension' => ['lwp'], 'mimeType' => [['extension' => 'lwp', 'mime' => 'application/vnd.lotus-wordpro']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5B50686F6E655D', 'offset' => '0', 'extension' => ['sam'], 'mimeType' => [['extension' => 'sam', 'mime' => 'application/vnd.lotus-wordpro']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '56657273696F6E20', 'offset' => '0', 'extension' => ['mif'], 'mimeType' => [['extension' => 'mif', 'mime' => 'application/vnd.mif']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3C3F786D6C2076657273696F6E3D22312E30223F3E', 'offset' => '0', 'extension' => ['xul'], 'mimeType' => [['extension' => 'xul', 'mime' => 'application/vnd.mozilla.xul+xml']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3026B2758E66CF11A6D900AA0062CE6C', 'offset' => '0', 'extension' => ['asf', 'wma', 'wmv'], 'mimeType' => [['extension' => 'asf', 'mime' => 'application/vnd.ms-asf'], ['extension' => 'wma', 'mime' => 'audio/x-ms-wma'], ['extension' => 'wmv', 'mime' => 'video/x-ms-wmv']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '49536328', 'offset' => '0', 'extension' => ['cab', 'hdr'], 'mimeType' => [['extension' => 'cab', 'mime' => 'application/vnd.ms-cab-compressed'], ['extension' => 'hdr', 'mime' => 'image/vnd.radiance']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D534346', 'offset' => '0', 'extension' => ['cab'], 'mimeType' => [['extension' => 'cab', 'mime' => 'application/vnd.ms-cab-compressed']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0908100000060500', 'offset' => '512', 'extension' => ['xls', 'pcx'], 'mimeType' => [['extension' => 'xls', 'mime' => 'application/vnd.ms-excel'], ['extension' => 'pcx', 'mime' => 'image/vnd.zbrush.pcx']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FDFFFFFF04', 'offset' => '512', 'extension' => ['xls', 'ppt'], 'mimeType' => [['extension' => 'xls', 'mime' => 'application/vnd.ms-excel'], ['extension' => 'ppt', 'mime' => 'application/vnd.ms-powerpoint']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FDFFFFFF20000000', 'offset' => '512', 'extension' => ['xls'], 'mimeType' => [['extension' => 'xls', 'mime' => 'application/vnd.ms-excel']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '49545346', 'offset' => '0', 'extension' => ['chm'], 'mimeType' => [['extension' => 'chm', 'mime' => 'application/vnd.ms-htmlhelp']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '006E1EF0', 'offset' => '512', 'extension' => ['ppt'], 'mimeType' => [['extension' => 'ppt', 'mime' => 'application/vnd.ms-powerpoint']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0F00E803', 'offset' => '512', 'extension' => ['ppt'], 'mimeType' => [['extension' => 'ppt', 'mime' => 'application/vnd.ms-powerpoint']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'A0461DF0', 'offset' => '512', 'extension' => ['ppt'], 'mimeType' => [['extension' => 'ppt', 'mime' => 'application/vnd.ms-powerpoint']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0E574B53', 'offset' => '0', 'extension' => ['wks'], 'mimeType' => [['extension' => 'wks', 'mime' => 'application/vnd.ms-works']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FF000200040405540200', 'offset' => '0', 'extension' => ['wks'], 'mimeType' => [['extension' => 'wks', 'mime' => 'application/vnd.ms-works']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D6963726F736F66742057696E646F7773204D6564696120506C61796572202D2D20', 'offset' => '84', 'extension' => ['wpl'], 'mimeType' => [['extension' => 'wpl', 'mime' => 'application/vnd.ms-wpl']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5B56657273696F6E', 'offset' => '2', 'extension' => ['cif'], 'mimeType' => [['extension' => 'cif', 'mime' => 'application/vnd.multiad.creator.cif']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B030414000600', 'offset' => '0', 'extension' => ['pptx', 'xlsx', 'docx'], 'mimeType' => [['extension' => 'pptx', 'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], ['extension' => 'xlsx', 'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], ['extension' => 'docx', 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '424F4F4B4D4F4249', 'offset' => '0', 'extension' => ['prc'], 'mimeType' => [['extension' => 'prc', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '74424D504B6E5772', 'offset' => '60', 'extension' => ['prc'], 'mimeType' => [['extension' => 'prc', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000000000000000000000000000000000000000000000000', 'offset' => '11', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D2D5720506F636B6574204469637469', 'offset' => '0', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D6963726F736F667420432F432B2B20', 'offset' => '0', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '736D5F', 'offset' => '0', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '737A657A', 'offset' => '0', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'ACED0005737200126267626C69747A2E', 'offset' => '0', 'extension' => ['pdb'], 'mimeType' => [['extension' => 'pdb', 'mime' => 'application/vnd.palm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00004D4D585052', 'offset' => '0', 'extension' => ['qxd'], 'mimeType' => [['extension' => 'qxd', 'mime' => 'application/vnd.Quark.QuarkXPress']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '526172211A0700', 'offset' => '0', 'extension' => ['rar'], 'mimeType' => [['extension' => 'rar', 'mime' => 'application/vnd.rar']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '526172211A070100', 'offset' => '0', 'extension' => ['rar'], 'mimeType' => [['extension' => 'rar', 'mime' => 'application/vnd.rar']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D4D4D440000', 'offset' => '0', 'extension' => ['mmf'], 'mimeType' => [['extension' => 'mmf', 'mime' => 'application/vnd.smaf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '52545353', 'offset' => '0', 'extension' => ['cap'], 'mimeType' => [['extension' => 'cap', 'mime' => 'application/vnd.tcpdump.pcap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '58435000', 'offset' => '0', 'extension' => ['cap'], 'mimeType' => [['extension' => 'cap', 'mime' => 'application/vnd.tcpdump.pcap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D444D5093A7', 'offset' => '0', 'extension' => ['dmp'], 'mimeType' => [['extension' => 'dmp', 'mime' => 'application/vnd.tcpdump.pcap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5041474544553634', 'offset' => '0', 'extension' => ['dmp'], 'mimeType' => [['extension' => 'dmp', 'mime' => 'application/vnd.tcpdump.pcap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5041474544554D50', 'offset' => '0', 'extension' => ['dmp'], 'mimeType' => [['extension' => 'dmp', 'mime' => 'application/vnd.tcpdump.pcap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FF575043', 'offset' => '0', 'extension' => ['wpd'], 'mimeType' => [['extension' => 'wpd', 'mime' => 'application/vnd.wordperfect']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '78617221', 'offset' => '0', 'extension' => ['xar'], 'mimeType' => [['extension' => 'xar', 'mime' => 'application/vnd.xara']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5350464900', 'offset' => '0', 'extension' => ['spf'], 'mimeType' => [['extension' => 'spf', 'mime' => 'application/vnd.yamaha.smaf-phrase']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0764743264647464', 'offset' => '0', 'extension' => ['dtd'], 'mimeType' => [['extension' => 'dtd', 'mime' => 'application/xml-dtd']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B030414000100630000000000', 'offset' => '0', 'extension' => ['zip'], 'mimeType' => [['extension' => 'zip', 'mime' => 'application/zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B0708', 'offset' => '0', 'extension' => ['zip'], 'mimeType' => [['extension' => 'zip', 'mime' => 'application/zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B4C495445', 'offset' => '30', 'extension' => ['zip'], 'mimeType' => [['extension' => 'zip', 'mime' => 'application/zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B537058', 'offset' => '526', 'extension' => ['zip'], 'mimeType' => [['extension' => 'zip', 'mime' => 'application/zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '152', 'offset' => '29', 'extension' => ['zip'], 'mimeType' => [['extension' => 'zip', 'mime' => 'application/zip']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2321414D52', 'offset' => '0', 'extension' => ['amr'], 'mimeType' => [['extension' => 'amr', 'mime' => 'audio/AMR']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2E736E64', 'offset' => '0', 'extension' => ['au'], 'mimeType' => [['extension' => 'au', 'mime' => 'audio/basic']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '646E732E', 'offset' => '0', 'extension' => ['au'], 'mimeType' => [['extension' => 'au', 'mime' => 'audio/basic']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00000020667479704D344120', 'offset' => '0', 'extension' => ['m4a'], 'mimeType' => [['extension' => 'm4a', 'mime' => 'audio/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '667479704D344120', 'offset' => '4', 'extension' => ['m4a'], 'mimeType' => [['extension' => 'm4a', 'mime' => 'audio/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '494433', 'offset' => '0', 'extension' => ['mp3'], 'mimeType' => [['extension' => 'mp3', 'mime' => 'audio/mpeg']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FFD8', 'offset' => '0', 'extension' => ['mp3', 'jpg', 'jpeg', 'jpe', 'jfif', 'mpeg', 'mpg'], 'mimeType' => [['extension' => 'mp3', 'mime' => 'audio/mpeg'], ['extension' => 'jpg', 'mime' => 'image/jpeg'], ['extension' => 'jpeg', 'mime' => 'image/jpeg'], ['extension' => 'jpe', 'mime' => 'image/jpeg'], ['extension' => 'jfif', 'mime' => 'image/jpeg'], ['extension' => 'mpeg', 'mime' => 'video/mpeg'], ['extension' => 'mpg', 'mime' => 'video/mpeg']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '52494646', 'offset' => '0', 'extension' => ['qcp', 'wav', 'webp', 'avi'], 'mimeType' => [['extension' => 'qcp', 'mime' => 'audio/qcelp'], ['extension' => 'wav', 'mime' => 'audio/x-wav'], ['extension' => 'webp', 'mime' => 'image/webp'], ['extension' => 'avi', 'mime' => 'video/x-msvideo']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '49443303000000', 'offset' => '0', 'extension' => ['koz'], 'mimeType' => [['extension' => 'koz', 'mime' => 'audio/vnd.audikoz']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '424D', 'offset' => '0', 'extension' => ['bmp', 'dib'], 'mimeType' => [['extension' => 'bmp', 'mime' => 'image/bmp'], ['extension' => 'dib', 'mime' => 'image/bmp']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '01000000', 'offset' => '0', 'extension' => ['emf'], 'mimeType' => [['extension' => 'emf', 'mime' => 'image/emf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '53494D504C4520203D202020202020202020202020202020202020202054', 'offset' => '0', 'extension' => ['fits'], 'mimeType' => [['extension' => 'fits', 'mime' => 'image/fits']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '474946383961', 'offset' => '0', 'extension' => ['gif'], 'mimeType' => [['extension' => 'gif', 'mime' => 'image/gif']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0000000C6A5020200D0A', 'offset' => '0', 'extension' => ['jp2'], 'mimeType' => [['extension' => 'jp2', 'mime' => 'image/jp2']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '89504E470D0A1A0A', 'offset' => '0', 'extension' => ['png'], 'mimeType' => [['extension' => 'png', 'mime' => 'image/png']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '492049', 'offset' => '0', 'extension' => ['tiff', 'tif'], 'mimeType' => [['extension' => 'tiff', 'mime' => 'image/tiff'], ['extension' => 'tif', 'mime' => 'image/tiff']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '49492A00', 'offset' => '0', 'extension' => ['tiff', 'tif'], 'mimeType' => [['extension' => 'tiff', 'mime' => 'image/tiff'], ['extension' => 'tif', 'mime' => 'image/tiff']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D4D002A', 'offset' => '0', 'extension' => ['tiff', 'tif'], 'mimeType' => [['extension' => 'tiff', 'mime' => 'image/tiff'], ['extension' => 'tif', 'mime' => 'image/tiff']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D4D002B', 'offset' => '0', 'extension' => ['tiff', 'tif'], 'mimeType' => [['extension' => 'tiff', 'mime' => 'image/tiff'], ['extension' => 'tif', 'mime' => 'image/tiff']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '38425053', 'offset' => '0', 'extension' => ['psd'], 'mimeType' => [['extension' => 'psd', 'mime' => 'image/vnd.adobe.photoshop']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '41433130', 'offset' => '0', 'extension' => ['dwg'], 'mimeType' => [['extension' => 'dwg', 'mime' => 'image/vnd.dwg']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00000100', 'offset' => '0', 'extension' => ['ico', 'mpeg', 'mpg', 'spl'], 'mimeType' => [['extension' => 'ico', 'mime' => 'image/vnd.microsoft.icon'], ['extension' => 'mpeg', 'mime' => 'video/mpeg'], ['extension' => 'mpg', 'mime' => 'video/mpeg'], ['extension' => 'spl', 'mime' => 'application/x-futuresplash']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4550', 'offset' => '0', 'extension' => ['mdi'], 'mimeType' => [['extension' => 'mdi', 'mime' => 'image/vnd.ms-modi']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '233F52414449414E43450A', 'offset' => '0', 'extension' => ['hdr'], 'mimeType' => [['extension' => 'hdr', 'mime' => 'image/vnd.radiance']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '010009000003', 'offset' => '0', 'extension' => ['wmf'], 'mimeType' => [['extension' => 'wmf', 'mime' => 'image/wmf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'D7CDC69A', 'offset' => '0', 'extension' => ['wmf'], 'mimeType' => [['extension' => 'wmf', 'mime' => 'image/wmf']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '46726F6D3A20', 'offset' => '0', 'extension' => ['eml'], 'mimeType' => [['extension' => 'eml', 'mime' => 'message/rfc822']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '52657475726E2D506174683A20', 'offset' => '0', 'extension' => ['eml'], 'mimeType' => [['extension' => 'eml', 'mime' => 'message/rfc822']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '582D', 'offset' => '0', 'extension' => ['eml'], 'mimeType' => [['extension' => 'eml', 'mime' => 'message/rfc822']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4A47040E', 'offset' => '0', 'extension' => ['art'], 'mimeType' => [['extension' => 'art', 'mime' => 'message/rfc822']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3C3F786D6C2076657273696F6E3D', 'offset' => '0', 'extension' => ['manifest'], 'mimeType' => [['extension' => 'manifest', 'mime' => 'text/cache-manifest']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2A2A2A2020496E7374616C6C6174696F6E205374617274656420', 'offset' => '0', 'extension' => ['log'], 'mimeType' => [['extension' => 'log', 'mime' => 'text/plain']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '424547494E3A56434152440D0A', 'offset' => '0', 'extension' => ['vcf'], 'mimeType' => [['extension' => 'vcf', 'mime' => 'text/vcard']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '444D5321', 'offset' => '0', 'extension' => ['dms'], 'mimeType' => [['extension' => 'dms', 'mime' => 'text/vnd.DMClientScript']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0000001466747970336770', 'offset' => '0', 'extension' => ['3gp', '3g2'], 'mimeType' => [['extension' => '3gp', 'mime' => 'video/3gpp'], ['extension' => '3g2', 'mime' => 'video/3gpp2']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0000002066747970336770', 'offset' => '0', 'extension' => ['3gp', '3g2'], 'mimeType' => [['extension' => '3gp', 'mime' => 'video/3gpp'], ['extension' => '3g2', 'mime' => 'video/3gpp2']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000000146674797069736F6D', 'offset' => '0', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000000186674797033677035', 'offset' => '0', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '0000001C667479704D534E56012900464D534E566D703432', 'offset' => '0', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6674797033677035', 'offset' => '4', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '667479704D534E56', 'offset' => '4', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6674797069736F6D', 'offset' => '4', 'extension' => ['mp4'], 'mimeType' => [['extension' => 'mp4', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00000018667479706D703432', 'offset' => '0', 'extension' => ['m4v'], 'mimeType' => [['extension' => 'm4v', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00000020667479704D345620', 'offset' => '0', 'extension' => ['m4v', 'flv'], 'mimeType' => [['extension' => 'm4v', 'mime' => 'video/mp4'], ['extension' => 'flv', 'mime' => 'video/x-flv']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '667479706D703432', 'offset' => '4', 'extension' => ['m4v'], 'mimeType' => [['extension' => 'm4v', 'mime' => 'video/mp4']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000001BA', 'offset' => '0', 'extension' => ['mpg'], 'mimeType' => [['extension' => 'mpg', 'mime' => 'video/mpeg']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '00', 'offset' => '0', 'extension' => ['mov'], 'mimeType' => [['extension' => 'mov', 'mime' => 'video/quicktime']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '000000146674797071742020', 'offset' => '0', 'extension' => ['mov'], 'mimeType' => [['extension' => 'mov', 'mime' => 'video/quicktime']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6674797071742020', 'offset' => '4', 'extension' => ['mov'], 'mimeType' => [['extension' => 'mov', 'mime' => 'video/quicktime']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6D6F6F76', 'offset' => '4', 'extension' => ['mov'], 'mimeType' => [['extension' => 'mov', 'mime' => 'video/quicktime']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4350543746494C45', 'offset' => '0', 'extension' => ['cpt'], 'mimeType' => [['extension' => 'cpt', 'mime' => 'application/mac-compactpro']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '43505446494C45', 'offset' => '0', 'extension' => ['cpt'], 'mimeType' => [['extension' => 'cpt', 'mime' => 'application/mac-compactpro']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '425A68', 'offset' => '0', 'extension' => ['bz2'], 'mimeType' => [['extension' => 'bz2', 'mime' => 'application/x-bzip2']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '454E5452595643440200000102001858', 'offset' => '0', 'extension' => ['vcd'], 'mimeType' => [['extension' => 'vcd', 'mime' => 'application/x-cdlink']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '6375736800000002000000', 'offset' => '0', 'extension' => ['csh'], 'mimeType' => [['extension' => 'csh', 'mime' => 'application/x-csh']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4A4152435300', 'offset' => '0', 'extension' => ['jar'], 'mimeType' => [['extension' => 'jar', 'mime' => 'application/x-java-archive']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '504B0304140008000800', 'offset' => '0', 'extension' => ['jar'], 'mimeType' => [['extension' => 'jar', 'mime' => 'application/x-java-archive']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5F27A889', 'offset' => '0', 'extension' => ['jar'], 'mimeType' => [['extension' => 'jar', 'mime' => 'application/x-java-archive']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'EDABEEDB', 'offset' => '0', 'extension' => ['rpm'], 'mimeType' => [['extension' => 'rpm', 'mime' => 'application/x-rpm']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '435753', 'offset' => '0', 'extension' => ['swf'], 'mimeType' => [['extension' => 'swf', 'mime' => 'application/x-shockwave-flash']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '465753', 'offset' => '0', 'extension' => ['swf'], 'mimeType' => [['extension' => 'swf', 'mime' => 'application/x-shockwave-flash']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5A5753', 'offset' => '0', 'extension' => ['swf'], 'mimeType' => [['extension' => 'swf', 'mime' => 'application/x-shockwave-flash']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5349542100', 'offset' => '0', 'extension' => ['sit'], 'mimeType' => [['extension' => 'sit', 'mime' => 'application/x-stuffit']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '5374756666497420286329313939372D', 'offset' => '0', 'extension' => ['sit'], 'mimeType' => [['extension' => 'sit', 'mime' => 'application/x-stuffit']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '7573746172', 'offset' => '257', 'extension' => ['tar'], 'mimeType' => [['extension' => 'tar', 'mime' => 'application/x-tar']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => 'FD377A585A00', 'offset' => '0', 'extension' => ['xz'], 'mimeType' => [['extension' => 'xz', 'mime' => 'application/x-xz']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '4D546864', 'offset' => '0', 'extension' => ['mid', 'midi'], 'mimeType' => [['extension' => 'mid', 'mime' => 'audio/midi'], ['extension' => 'midi', 'mime' => 'audio/midi']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '464F524D00', 'offset' => '0', 'extension' => ['aiff'], 'mimeType' => [['extension' => 'aiff', 'mime' => 'audio/x-aiff']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '664C614300000022', 'offset' => '0', 'extension' => ['flac'], 'mimeType' => [['extension' => 'flac', 'mime' => 'audio/x-flac']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '727473703A2F2F', 'offset' => '0', 'extension' => ['ram'], 'mimeType' => [['extension' => 'ram', 'mime' => 'audio/x-pn-realaudio']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2E524D46', 'offset' => '0', 'extension' => ['rm'], 'mimeType' => [['extension' => 'rm', 'mime' => 'audio/x-pn-realaudio']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2E524D460000001200', 'offset' => '0', 'extension' => ['ra'], 'mimeType' => [['extension' => 'ra', 'mime' => 'audio/x-realaudio']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '2E7261FD00', 'offset' => '0', 'extension' => ['ra'], 'mimeType' => [['extension' => 'ra', 'mime' => 'audio/x-realaudio']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '50350A', 'offset' => '0', 'extension' => ['pgm'], 'mimeType' => [['extension' => 'pgm', 'mime' => 'image/x-portable-graymap']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '01DA01010003', 'offset' => '0', 'extension' => ['rgb'], 'mimeType' => [['extension' => 'rgb', 'mime' => 'image/x-rgb']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '1A45DFA3', 'offset' => '0', 'extension' => ['webm', 'mkv'], 'mimeType' => [['extension' => 'webm', 'mime' => 'video/webm'], ['extension' => 'mkv', 'mime' => 'video/x-matroska']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '464C5601', 'offset' => '0', 'extension' => ['flv'], 'mimeType' => [['extension' => 'flv', 'mime' => 'video/x-flv']], 'file_class' => EntropyResult::COMPRESSED],
+ ['byteSequence' => '3C', 'offset' => '0', 'extension' => ['asx'], 'mimeType' => [['extension' => 'asx', 'mime' => 'video/x-ms-asf']], 'file_class' => EntropyResult::COMPRESSED],
+ ];
+
+ /**
+ * @var array
+ */
+ public static function getSignatures()
+ {
+ return self::$signatures;
+ }
+}
diff --git a/lib/Monitor.php b/lib/Monitor.php
new file mode 100644
index 0000000..a0009fa
--- /dev/null
+++ b/lib/Monitor.php
@@ -0,0 +1,462 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Analyzer\EntropyAnalyzer;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Analyzer\FileCorruptionAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileNameAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\Storage\IStorage;
+use OCP\Notification\IManager;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IRequest;
+
+class Monitor
+{
+ /** File access
+ * @var int
+ */
+ const DELETE = 1;
+ const RENAME = 2;
+ const WRITE = 3;
+ const READ = 4;
+ const CREATE = 5;
+
+ /** @var IRequest */
+ protected $request;
+
+ /** @var IConfig */
+ protected $config;
+
+ /** @var ITimeFactory */
+ protected $time;
+
+ /** @var IAppManager */
+ protected $appManager;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IRootFolder */
+ protected $rootFolder;
+
+ /** @var EntropyAnalyzer */
+ protected $entropyAnalyzer;
+
+ /** @var FileOperationMapper */
+ protected $mapper;
+
+ /** @var FileNameAnalyzer */
+ protected $fileNameAnalyzer;
+
+ /** @var FileCorruptionAnalyzer */
+ protected $fileCorruptionAnalyzer;
+
+ /** @var string */
+ protected $userId;
+
+ /** @var int */
+ protected $nestingLevel = 0;
+
+ /**
+ * @param IRequest $request
+ * @param IConfig $config
+ * @param ITimeFactory $time
+ * @param IAppManager $appManager
+ * @param ILogger $logger
+ * @param IRootFolder $rootFolder
+ * @param EntropyAnalyzer $entropyAnalyzer
+ * @param FileOperationMapper $mapper
+ * @param FileNameAnalyzer $fileNameAnalyzer
+ * @param FileCorruptionAnalyzer $fileCorruptionAnalyzer
+ * @param string $userId
+ */
+ public function __construct(
+ IRequest $request,
+ IConfig $config,
+ ITimeFactory $time,
+ IAppManager $appManager,
+ ILogger $logger,
+ IRootFolder $rootFolder,
+ EntropyAnalyzer $entropyAnalyzer,
+ FileOperationMapper $mapper,
+ FileNameAnalyzer $fileNameAnalyzer,
+ FileCorruptionAnalyzer $fileCorruptionAnalyzer,
+ $userId
+ ) {
+ $this->request = $request;
+ $this->config = $config;
+ $this->time = $time;
+ $this->appManager = $appManager;
+ $this->logger = $logger;
+ $this->rootFolder = $rootFolder;
+ $this->entropyAnalyzer = $entropyAnalyzer;
+ $this->mapper = $mapper;
+ $this->fileNameAnalyzer = $fileNameAnalyzer;
+ $this->fileCorruptionAnalyzer = $fileCorruptionAnalyzer;
+ $this->userId = $userId;
+ }
+
+ /**
+ * Analyze file.
+ *
+ * @param IStorage $storage
+ * @param array $paths
+ * @param int $mode
+ */
+ public function analyze(IStorage $storage, $paths, $mode)
+ {
+ $path = $paths[0];
+ if ($this->userId === null || $this->nestingLevel !== 0 || !$this->isUploadedFile($storage, $path) || $this->isCreatingSkeletonFiles()) {
+ // check only cloud files and no system files
+ return;
+ }
+
+ if (!$this->request->isUserAgent([
+ IRequest::USER_AGENT_CLIENT_DESKTOP,
+ IRequest::USER_AGENT_CLIENT_ANDROID,
+ IRequest::USER_AGENT_CLIENT_IOS,
+ ])) {
+ // not a sync client
+ return;
+ }
+
+ $this->nestingLevel++;
+
+ switch ($mode) {
+ case self::RENAME:
+ if (preg_match('/.+\.d[0-9]+/', pathinfo($paths[1])['basename']) > 0) {
+ return;
+ }
+ // reset PROPFIND_COUNT
+ $this->resetProfindCount();
+
+ try {
+ $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent();
+ $node = $userRoot->get($path);
+ } catch (\OCP\Files\NotFoundException $exception) {
+ return;
+ }
+
+ // not a file no need to analyze
+ if (!($node instanceof File)) {
+ $this->addFolderOperation($paths, $node, self::RENAME);
+ $this->nestingLevel--;
+
+ return;
+ }
+
+ $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $this->addFileOperation($paths, $node, self::RENAME);
+
+ $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
+
+ $this->nestingLevel--;
+
+ return;
+ case self::WRITE:
+ // reset PROPFIND_COUNT
+ $this->resetProfindCount();
+
+ try {
+ $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent();
+ $node = $userRoot->get($path);
+ } catch (\OCP\Files\NotFoundException $exception) {
+ return;
+ }
+
+ // not a file no need to analyze
+ if (!($node instanceof File)) {
+ $this->addFolderOperation($paths, $node, self::WRITE);
+ $this->nestingLevel--;
+
+ return;
+ }
+
+ $this->addFileOperation($paths, $node, self::WRITE);
+
+ $this->nestingLevel--;
+
+ return;
+ case self::READ:
+ $this->nestingLevel--;
+
+ return;
+ case self::DELETE:
+ // reset PROPFIND_COUNT
+ $this->resetProfindCount();
+
+ try {
+ $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent();
+ $node = $userRoot->get($path);
+ } catch (\OCP\Files\NotFoundException $exception) {
+ return;
+ }
+
+ // not a file no need to analyze
+ if (!($node instanceof File)) {
+ $this->addFolderOperation($paths, $node, self::DELETE);
+ $this->nestingLevel--;
+
+ return;
+ }
+
+ $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_SHARED);
+
+ $this->addFileOperation($paths, $node, self::DELETE);
+
+ $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
+ $this->nestingLevel--;
+
+ return;
+ case self::CREATE:
+ // only folders are created
+
+ // reset PROPFIND_COUNT
+ $this->resetProfindCount();
+
+ $fileOperation = new FileOperation();
+ $fileOperation->setUserId($this->userId);
+ $fileOperation->setPath(str_replace('files', '', pathinfo($path)['dirname']));
+ $fileOperation->setOriginalName(pathinfo($path)['basename']);
+ $fileOperation->setType('folder');
+ $fileOperation->setMimeType('httpd/unix-directory');
+ $fileOperation->setSize(0);
+ $fileOperation->setTimestamp(time());
+ $fileOperation->setCorrupted(false);
+ $fileOperation->setCommand(self::CREATE);
+ $sequenceId = $this->config->getUserValue($this->userId, Application::APP_ID, 'sequence_id', 0);
+ $fileOperation->setSequence($sequenceId);
+
+ // entropy analysis
+ $fileOperation->setEntropy(0.0);
+ $fileOperation->setStandardDeviation(0.0);
+ $fileOperation->setFileClass(EntropyResult::NORMAL);
+
+ // file name analysis
+ $fileOperation->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation->setFileNameEntropy(0.0);
+
+ $this->mapper->insert($fileOperation);
+ $this->nestingLevel--;
+
+ return;
+ default:
+ $this->nestingLevel--;
+
+ return;
+ }
+ }
+
+ /**
+ * Return file size of a path.
+ *
+ * @param string $path
+ *
+ * @return int
+ */
+ private function getFileSize($path)
+ {
+ if (strpos($path, 'files_trashbin') !== false) {
+ $node = $this->rootFolder->get($path);
+
+ if (!($node instanceof File)) {
+ throw new NotFoundException();
+ }
+
+ return $node->getSize();
+ } else {
+ $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent();
+ $node = $userRoot->get($path);
+
+ if (!($node instanceof File)) {
+ throw new NotFoundException();
+ }
+
+ return $node->getSize();
+ }
+ }
+
+ /**
+ * Check if file is a uploaded file.
+ *
+ * @param IStorage $storage
+ * @param string $path
+ *
+ * @return bool
+ */
+ private function isUploadedFile(IStorage $storage, $path)
+ {
+ $fullPath = $path;
+ if (property_exists($storage, 'mountPoint')) {
+ /* @var StorageWrapper $storage */
+ $fullPath = $storage->mountPoint.$path;
+ }
+
+ // ignore transfer files
+ if (strpos($fullPath, 'ocTransferId') > 0) {
+ return false;
+ }
+
+ if (substr_count($fullPath, '/') < 3) {
+ return false;
+ }
+
+ // '', admin, 'files', 'path/to/file.txt'
+ $segment = explode('/', $fullPath, 4);
+
+ return isset($segment[2]) && in_array($segment[2], [
+ 'files',
+ 'thumbnails',
+ 'files_versions',
+ ], true);
+ }
+
+ /**
+ * Check if we are in the LoginController and if so, ignore the firewall.
+ *
+ * @return bool
+ */
+ protected function isCreatingSkeletonFiles()
+ {
+ $exception = new \Exception();
+ $trace = $exception->getTrace();
+ foreach ($trace as $step) {
+ if (isset($step['class'], $step['function']) &&
+ $step['class'] === 'OC\Core\Controller\LoginController' &&
+ $step['function'] === 'tryLogin') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Reset PROPFIND_COUNT.
+ */
+ protected function resetProfindCount()
+ {
+ $userKeys = $this->config->getUserKeys($this->userId, Application::APP_ID);
+ foreach ($userKeys as $key) {
+ if (strpos($key, 'propfind_count') !== false) {
+ $this->config->deleteUserValue($this->userId, Application::APP_ID, $key);
+ }
+ }
+ }
+
+ /**
+ * Add a folder to the operations.
+ *
+ * @param array $paths
+ * @param INode $node
+ * @param int $operation
+ */
+ private function addFolderOperation($paths, $node, $operation)
+ {
+ $fileOperation = new FileOperation();
+ $fileOperation->setUserId($this->userId);
+ $fileOperation->setPath(str_replace('files', '', pathinfo($node->getInternalPath())['dirname']));
+ $fileOperation->setOriginalName($node->getName());
+ if ($operation === self::RENAME) {
+ $fileOperation->setNewName(pathinfo($paths[1])['basename']);
+ }
+ $fileOperation->setType('folder');
+ $fileOperation->setMimeType($node->getMimeType());
+ $fileOperation->setSize(0);
+ $fileOperation->setTimestamp(time());
+ $fileOperation->setCorrupted(false);
+ $fileOperation->setCommand($operation);
+ $sequenceId = $this->config->getUserValue($this->userId, Application::APP_ID, 'sequence_id', 0);
+ $fileOperation->setSequence($sequenceId);
+
+ // entropy analysis
+ $fileOperation->setEntropy(0.0);
+ $fileOperation->setStandardDeviation(0.0);
+ $fileOperation->setFileClass(EntropyResult::NORMAL);
+
+ // file name analysis
+ $fileOperation->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation->setFileNameEntropy(0.0);
+
+ $this->mapper->insert($fileOperation);
+ }
+
+ /**
+ * Add a file to the operations.
+ *
+ * @param array $paths
+ * @param INode $node
+ * @param int $operation
+ */
+ private function addFileOperation($paths, $node, $operation)
+ {
+ $fileOperation = new FileOperation();
+ $fileOperation->setUserId($this->userId);
+ $fileOperation->setPath(str_replace('files', '', pathinfo($node->getInternalPath())['dirname']));
+ $fileOperation->setOriginalName($node->getName());
+ if ($operation === self::RENAME) {
+ $fileOperation->setNewName(pathinfo($paths[1])['basename']);
+ }
+ $fileOperation->setType('file');
+ $fileOperation->setMimeType($node->getMimeType());
+ $fileOperation->setSize($node->getSize());
+ $fileOperation->setTimestamp(time());
+ $fileOperation->setCommand($operation);
+ $sequenceId = $this->config->getUserValue($this->userId, Application::APP_ID, 'sequence_id', 0);
+ $fileOperation->setSequence($sequenceId);
+
+ // file name analysis
+ $fileNameResult = $this->fileNameAnalyzer->analyze($node->getInternalPath());
+ $fileOperation->setFileNameClass($fileNameResult->getFileNameClass());
+ $fileOperation->setFileNameEntropy($fileNameResult->getEntropyOfFileName());
+
+ $fileCorruptionResult = $this->fileCorruptionAnalyzer->analyze($node);
+ $fileOperation->setCorrupted($fileCorruptionResult->isCorrupted());
+
+ // entropy analysis
+ $entropyResult = $this->entropyAnalyzer->analyze($node);
+ $fileOperation->setEntropy($entropyResult->getEntropy());
+ $fileOperation->setStandardDeviation($entropyResult->getStandardDeviation());
+ if ($fileCorruptionResult->isCorrupted()) {
+ $fileOperation->setFileClass($entropyResult->getFileClass());
+ } else {
+ if ($fileCorruptionResult->getFileClass() !== -1) {
+ $fileOperation->setFileClass($fileCorruptionResult->getFileClass());
+ }
+ }
+
+ $entity = $this->mapper->insert($fileOperation);
+ }
+}
diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php
new file mode 100644
index 0000000..b7a19bd
--- /dev/null
+++ b/lib/Notification/Notifier.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Notification;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Notification\IManager;
+use OCP\Notification\INotification;
+use OCP\Notification\INotifier;
+use OCP\IConfig;
+
+class Notifier implements INotifier
+{
+ /** @var IConfig */
+ private $config;
+
+ /** @var IFactory */
+ protected $l10nFactory;
+
+ /** @var IUserManager */
+ protected $userManager;
+
+ /** @var IManager */
+ protected $notificationManager;
+
+ /** @var IURLGenerator */
+ protected $urlGenerator;
+
+ /**
+ * @param IConfig $config
+ * @param IFactory $l10nFactory
+ * @param IUserManager $userManager
+ * @param IManager $notificationManager
+ * @param IURLGenerator $urlGenerator
+ */
+ public function __construct(
+ IConfig $config,
+ IFactory $l10nFactory,
+ IUserManager $userManager,
+ IManager $notificationManager,
+ IURLGenerator $urlGenerator
+ ) {
+ $this->config = $config;
+ $this->l10nFactory = $l10nFactory;
+ $this->userManager = $userManager;
+ $this->notificationManager = $notificationManager;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * @param INotification $notification
+ * @param string $languageCode
+ *
+ * @return INotification
+ */
+ public function prepare(INotification $notification, $languageCode)
+ {
+ if ($notification->getApp() !== Application::APP_ID) {
+ // Not my app => throw
+ throw new \InvalidArgumentException('Unknown app');
+ }
+
+ //Read the language from the notification
+ $l = $this->l10nFactory->get(Application::APP_ID, $languageCode);
+
+ switch ($notification->getSubject()) {
+ case 'ransomware_attack_detected':
+ $message = 'Detected a sequence of suspicious file operations.';
+ $notification->setParsedSubject($l->t('Detected suspicious file operations.', $notification->getSubjectParameters()));
+ $notification->setParsedMessage($l->t($message, $notification->getMessageParameters()));
+ $notification->setIcon($this->urlGenerator->imagePath('ransomware_detection', 'app-dark.svg'));
+
+ return $notification;
+ default:
+ throw new \InvalidArgumentException('Unknown subject');
+ }
+ }
+}
diff --git a/lib/Service/FileOperationService.php b/lib/Service/FileOperationService.php
new file mode 100644
index 0000000..049de93
--- /dev/null
+++ b/lib/Service/FileOperationService.php
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Service;
+
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+
+class FileOperationService
+{
+ /** @var FileOperationMapper */
+ protected $mapper;
+
+ /** @var string */
+ protected $userId;
+
+ /**
+ * @param FileOperationMapper $mapper
+ * @param string $userId
+ */
+ public function __construct(
+ FileOperationMapper $mapper,
+ $userId
+ ) {
+ $this->mapper = $mapper;
+ $this->userId = $userId;
+ }
+
+ /**
+ * Find one by the id.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @param int $id
+ *
+ * @return Entity
+ */
+ public function find($id)
+ {
+ return $this->mapper->find($id, $this->userId);
+ }
+
+ /**
+ * Find one by the file name.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @param string $name
+ *
+ * @return Entity
+ */
+ public function findOneByFileName($name)
+ {
+ return $this->mapper->findOneByFileName($name, $this->userId);
+ }
+
+ /**
+ * Find one with the highest id.
+ *
+ * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
+ * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
+ *
+ * @return Entity
+ */
+ public function findOneWithHighestId()
+ {
+ return $this->mapper->findOneWithHighestId($this->userId);
+ }
+
+ /**
+ * Find all.
+ *
+ * @param array $params
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ public function findAll(array $params = [], $limit = null, $offset = null)
+ {
+ array_push($params, $this->userId);
+
+ return $this->mapper->findAll($params, $limit, $offset);
+ }
+
+ /**
+ * Find sequence by id.
+ *
+ * @param array $params
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ public function findSequenceById(array $params = [], $limit = null, $offset = null)
+ {
+ array_push($params, $this->userId);
+
+ return $this->mapper->findSequenceById($params, $limit, $offset);
+ }
+
+ /**
+ * Delete one by id.
+ *
+ * @param int $id
+ */
+ public function deleteById($id)
+ {
+ $this->mapper->deleteById($id, $this->userId);
+ }
+
+ /**
+ * Delete sequence by id.
+ *
+ * @param int $sequence
+ */
+ public function deleteSequenceById($sequence)
+ {
+ $this->mapper->deleteSequenceById($sequence, $this->userId);
+ }
+
+ /**
+ * Delete all entries before $timestamp.
+ *
+ * @param int $timestamp
+ */
+ public function deleteFileOperationsBefore($timestamp)
+ {
+ $this->mapper->deleteFileOperationsBefore($timestamp);
+ }
+}
diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php
new file mode 100644
index 0000000..efd0296
--- /dev/null
+++ b/lib/Settings/Admin.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Settings;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\Settings\ISettings;
+
+class Admin implements ISettings
+{
+ /** @var IConfig */
+ protected $config;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(
+ IConfig $config
+ ) {
+ $this->config = $config;
+ }
+
+ /**
+ * @return TemplateResponse returns the instance with all parameters set, ready to be rendered
+ *
+ * @since 9.1
+ */
+ public function getForm()
+ {
+ $suspicionLevel = $this->config->getAppValue(Application::APP_ID, 'suspicionLevel', 3);
+
+ if ($suspicionLevel == 1) {
+ $activeSuspicionLevel = ['code' => 1, 'name' => 'Low'];
+ $suspicionLevels = [['code' => 2, 'name' => 'Middle'], ['code' => 3, 'name' => 'High']];
+ } elseif ($suspicionLevel == 2) {
+ $activeSuspicionLevel = ['code' => 2, 'name' => 'Middle'];
+ $suspicionLevels = [['code' => 1, 'name' => 'Low'], ['code' => 3, 'name' => 'High']];
+ } elseif ($suspicionLevel == 3) {
+ $activeSuspicionLevel = ['code' => 3, 'name' => 'High'];
+ $suspicionLevels = [['code' => 2, 'name' => 'Middle'], ['code' => 1, 'name' => 'Low']];
+ } else {
+ $activeSuspicionLevel = [];
+ $suspicionLevels = [];
+ }
+
+ return new TemplateResponse(Application::APP_ID, 'admin', [
+ 'minimumSequenceLength' => $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 5),
+ 'activeSuspicionLevel' => $activeSuspicionLevel,
+ 'suspicionLevels' => $suspicionLevels,
+ 'expireDays' => $this->config->getAppValue(Application::APP_ID, 'expire_days', 7),
+ ], '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ *
+ * @since 9.1
+ */
+ public function getSection()
+ {
+ return Application::APP_ID;
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ *
+ * @since 9.1
+ */
+ public function getPriority()
+ {
+ return 1;
+ }
+}
diff --git a/lib/Settings/AdminSection.php b/lib/Settings/AdminSection.php
new file mode 100644
index 0000000..404b710
--- /dev/null
+++ b/lib/Settings/AdminSection.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Settings;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\IURLGenerator;
+use OCP\IL10N;
+use OCP\Settings\IIconSection;
+
+class AdminSection implements IIconSection
+{
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IL10N */
+ private $l;
+
+ public function __construct(
+ IURLGenerator $urlGenerator,
+ IL10N $l
+ ) {
+ $this->urlGenerator = $urlGenerator;
+ $this->l = $l;
+ }
+
+ /**
+ * returns the relative path to an 16*16 icon describing the section.
+ * e.g. '/core/img/places/files.svg'.
+ *
+ * @returns string
+ *
+ * @since 13.0.0
+ */
+ public function getIcon()
+ {
+ return $this->urlGenerator->imagePath('ransomware_detection', 'app-dark.svg');
+ }
+
+ /**
+ * returns the ID of the section. It is supposed to be a lower case string.
+ *
+ * @returns string
+ */
+ public function getID()
+ {
+ return Application::APP_ID;
+ }
+
+ /**
+ * returns the translated name as it should be displayed, e.g. 'LDAP / AD
+ * integration'. Use the L10N service to translate it.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->l->t('Ransomware detection');
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the settings navigation. The sections are arranged in ascending order of
+ * the priority values. It is required to return a value between 0 and 99.
+ */
+ public function getPriority()
+ {
+ return 15;
+ }
+}
diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php
new file mode 100644
index 0000000..88211e6
--- /dev/null
+++ b/lib/Settings/Personal.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Settings;
+
+use OCP\Settings\ISettings;
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
+use OCP\IL10N;
+
+class Personal implements ISettings
+{
+ /** @var IConfig */
+ protected $config;
+
+ /** @var ITimeFactory */
+ protected $time;
+
+ /** @var IL10N */
+ protected $l10n;
+
+ /** @var string */
+ protected $userId;
+
+ /**
+ * @param IConfig $config
+ * @param ITimeFactory $time
+ * @param IL10N $l10n
+ * @param string $userId
+ */
+ public function __construct(
+ IConfig $config,
+ ITimeFactory $time,
+ IL10N $l10n,
+ $userId
+ ) {
+ $this->config = $config;
+ $this->time = $time;
+ $this->l10n = $l10n;
+ $this->userId = $userId;
+ }
+
+ /**
+ * @return TemplateResponse returns the instance with all parameters set, ready to be rendered
+ *
+ * @since 9.1
+ */
+ public function getForm()
+ {
+ $colorMode = $this->config->getUserValue($this->userId, Application::APP_ID, 'colorMode', 0);
+
+ if ($colorMode == 0) {
+ $colorActive = ['code' => 0, 'name' => 'Normal'];
+ $color = ['code' => 1, 'name' => 'Color blind'];
+ } else {
+ $colorActive = ['code' => 1, 'name' => 'Color blind'];
+ $color = ['code' => 0, 'name' => 'Normal'];
+ }
+
+ return new TemplateResponse(Application::APP_ID, 'personal', [
+ 'colorActive' => $colorActive,
+ 'color' => $color,
+ ], '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'ransomware_detection'
+ *
+ * @since 9.1
+ */
+ public function getSection()
+ {
+ return Application::APP_ID;
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ *
+ * @since 9.1
+ */
+ public function getPriority()
+ {
+ return 40;
+ }
+}
diff --git a/lib/Settings/PersonalSection.php b/lib/Settings/PersonalSection.php
new file mode 100644
index 0000000..23415af
--- /dev/null
+++ b/lib/Settings/PersonalSection.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Settings;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\Settings\IIconSection;
+
+class PersonalSection implements IIconSection
+{
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IL10N */
+ private $l;
+
+ public function __construct(
+ IURLGenerator $urlGenerator,
+ IL10N $l
+ ) {
+ $this->urlGenerator = $urlGenerator;
+ $this->l = $l;
+ }
+
+ /**
+ * returns the relative path to an 16*16 icon describing the section.
+ * e.g. '/core/img/places/files.svg'.
+ *
+ * @returns string
+ *
+ * @since 13.0.0
+ */
+ public function getIcon()
+ {
+ return $this->urlGenerator->imagePath('ransomware_detection', 'app-dark.svg');
+ }
+
+ /**
+ * returns the ID of the section. It is supposed to be a lower case string,
+ * e.g. 'ldap'.
+ *
+ * @returns string
+ *
+ * @since 9.1
+ */
+ public function getID()
+ {
+ return Application::APP_ID;
+ }
+
+ /**
+ * returns the translated name as it should be displayed, e.g. 'LDAP / AD
+ * integration'. Use the L10N service to translate it.
+ *
+ * @return string
+ *
+ * @since 9.1
+ */
+ public function getName()
+ {
+ return $this->l->t('Ransomware detection');
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the settings navigation. The sections are arranged in ascending order of
+ * the priority values. It is required to return a value between 0 and 99.
+ *
+ * E.g.: 70
+ *
+ * @since 9.1
+ */
+ public function getPriority()
+ {
+ return 15;
+ }
+}
diff --git a/lib/StorageWrapper.php b/lib/StorageWrapper.php
new file mode 100644
index 0000000..6809081
--- /dev/null
+++ b/lib/StorageWrapper.php
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2017 Matthias Held <matthias.held@uni-konstanz.de>
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection;
+
+use OC\Files\Storage\Wrapper\Wrapper;
+use OCP\Files\Storage\IStorage;
+
+class StorageWrapper extends Wrapper
+{
+ /** @var Monitor */
+ protected $monitor;
+
+ /** @var string */
+ public $mountPoint;
+
+ /**
+ * @param array $parameters
+ */
+ public function __construct(
+ $parameters
+ ) {
+ parent::__construct($parameters);
+ $this->monitor = $parameters['monitor'];
+ $this->mountPoint = $parameters['mountPoint'];
+ }
+
+ /**
+ * Makes it easier to test.
+ *
+ * @param IStorage $storage
+ * @param string $path
+ * @param int $mode
+ */
+ protected function analyze(IStorage $storage, $path, $mode)
+ {
+ return $this->monitor->analyze($storage, $path, $mode);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.mkdir.php.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function mkdir($path)
+ {
+ $this->analyze($this, [$path], Monitor::CREATE);
+
+ return $this->storage->mkdir($path);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.rmdir.php.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function rmdir($path)
+ {
+ $this->analyze($this, [$path], Monitor::DELETE);
+
+ return $this->storage->rmdir($path);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.file_get_contents.php.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function file_get_contents($path)
+ {
+ $this->analyze($this, [$path], Monitor::READ);
+
+ return $this->storage->file_get_contents($path);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.file_put_contents.php.
+ *
+ * @param string $path
+ * @param string $data
+ *
+ * @return bool
+ */
+ public function file_put_contents($path, $data)
+ {
+ $this->analyze($this, [$path], Monitor::WRITE);
+
+ return $this->storage->file_put_contents($path, $data);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.unlink.php.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function unlink($path)
+ {
+ $this->analyze($this, [$path], Monitor::DELETE);
+
+ return $this->storage->unlink($path);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.rename.php.
+ *
+ * @param string $path1
+ * @param string $path2
+ *
+ * @return bool
+ */
+ public function rename($path1, $path2)
+ {
+ $this->analyze($this, [$path1, $path2], Monitor::RENAME);
+
+ return $this->storage->rename($path1, $path2);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.copy.php.
+ *
+ * @param string $path1
+ * @param string $path2
+ *
+ * @return bool
+ */
+ public function copy($path1, $path2)
+ {
+ $this->analyze($this, [$path1, $path2], Monitor::WRITE);
+
+ return $this->storage->copy($path1, $path2);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.fopen.php.
+ *
+ * @param string $path
+ * @param string $mode
+ *
+ * @return resource
+ */
+ public function fopen($path, $mode)
+ {
+ $fileMode = Monitor::READ;
+ switch ($mode) {
+ case 'r+':
+ case 'rb+':
+ case 'w+':
+ case 'wb+':
+ case 'x+':
+ case 'xb+':
+ case 'a+':
+ case 'ab+':
+ case 'w':
+ case 'wb':
+ case 'x':
+ case 'xb':
+ case 'a':
+ case 'ab':
+ $fileMode = Monitor::WRITE;
+ }
+ $this->analyze($this, [$path], $fileMode);
+
+ return $this->storage->fopen($path, $mode);
+ }
+
+ /**
+ * see http://php.net/manual/en/function.touch.php
+ * If the backend does not support the operation, false should be returned.
+ *
+ * @param string $path
+ * @param int $mtime
+ *
+ * @return bool
+ */
+ public function touch($path, $mtime = null)
+ {
+ $this->analyze($this, [$path], Monitor::WRITE);
+
+ return $this->storage->touch($path, $mtime);
+ }
+
+ /**
+ * get a cache instance for the storage.
+ *
+ * @param string $path
+ * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
+ *
+ * @return \OC\Files\Cache\Cache
+ */
+ public function getCache($path = '', $storage = null)
+ {
+ if (!$storage) {
+ $storage = $this;
+ }
+ $cache = $this->storage->getCache($path, $storage);
+
+ return new CacheWrapper($cache, $storage, $this->monitor);
+ }
+
+ /**
+ * @param \OCP\Files\Storage $sourceStorage
+ * @param string $sourceInternalPath
+ * @param string $targetInternalPath
+ *
+ * @return bool
+ */
+ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath)
+ {
+ if ($sourceStorage === $this) {
+ return $this->copy($sourceInternalPath, $targetInternalPath);
+ }
+ $this->analyze($this, [$targetInternalPath], Monitor::WRITE);
+
+ return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+
+ /**
+ * @param \OCP\Files\Storage $sourceStorage
+ * @param string $sourceInternalPath
+ * @param string $targetInternalPath
+ *
+ * @return bool
+ */
+ public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath)
+ {
+ if ($sourceStorage === $this) {
+ return $this->rename($sourceInternalPath, $targetInternalPath);
+ }
+ $this->analyze($this, [$targetInternalPath], Monitor::WRITE);
+
+ return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+}