diff options
author | Matthias Held <ilovemilk@wusa.io> | 2018-06-18 15:14:17 +0300 |
---|---|---|
committer | Matthias Held <ilovemilk@wusa.io> | 2018-06-18 15:14:17 +0300 |
commit | 0d4208bd4934d83654fc3893867b2557546b404a (patch) | |
tree | b6db2416bb0da30e119fdf8ff2120dea7d086481 /lib | |
parent | 7a756a94ab887209f7ad7ffc6a01e2d16d01bfd4 (diff) |
Add Nextcloud application
Diffstat (limited to 'lib')
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); + } +} |