2
\$\begingroup\$

Nowadays there are many malware which is a batch file (.bat) or batch file is directly or indirectly helps to download, execute malicious process/executable etc here is a single header and source implementation which checks and informs whether the batch file is safe to execute or not.

Here is the header implementation:

// https://github.com/VISWESWARAN1998/SIVA-AV/tree/master/static%20checker
#pragma once
#include<iostream>
#include<string>
#include<fstream>
#include<vector>
#include<map>
#include<iterator>
#include<regex>

class Bat
{
private:
    std::wstring fileName;

    bool isAttributesChanged = false;
    bool isContentDeleted = false;
    bool isDecodingOrEncodingOtherFile = false;
    bool isDirectoriesCreated = false;
    bool isFileDownloaded = false;
    bool isFirewallGetsAccessed = false;
    bool isHidingContent = false;
    bool isSystemLocationModified = false;
    bool isNetWorkUsed = false;
    bool isOtherFileExcuted = false;
    bool isPowershellUsed = false;
    bool isRegisteryUsed = false;
    bool isTaskTerminated = false;
    //unsigned int isMalicious = 0;

    // This map will contain the description for a particular line which
    // does something unusual, same line can contain multiple commands so I've used multimap
    std::multimap<unsigned int, std::string> description;
    // This map will contain resoures like urls involved, ip address involved etc.,
    //std::map<std::string, std::string> resources;

public:
    /*constructor to initialize the file name*/
    Bat(std::wstring fileName);
    /*A method to analyse the file*/
    bool analyseFile();
    /*
    Sometimes, an attacker will add multiple lines of command in a sinlgle line of code
    like this: 
            echo "I wish to do good for you!" & call malware.bat 
    this method helps you to split it into individual commands like this,

    echo "I wish to do good for you!"
    call malware.bat
    */
    std::vector<std::string> getCommands(std::string line);
    // The method will perform an analysis on that command
    void analyseCommand(unsigned int lineNo, std::string command);
    // This method will return true if regex and the code gets matches
    bool isRegexMatched(std::string code, std::string pattern);

    // Getters
    // Will return the description
    std::multimap<unsigned int, std::string> getDescription() { return description; }
    // Will return true if attributes is changed
    bool attribChange() { return isAttributesChanged; }
    // Will return true if content is deleted
    bool contentDeleted() { return isContentDeleted; }
    // Will return true if decoding or encoding occured
    bool encodingDecodingOccred() { return isDecodingOrEncodingOtherFile; }
    // Will return true if directories is created
    bool newDirectories() { return isDirectoriesCreated; }
    // Will return true if file is downloaded from the internet
    bool fileDownloads() { return isFileDownloaded; }
    // Will return true if firewall is being used
    bool firewallAccess() { return isFirewallGetsAccessed; }
    // Will return true if it hides content from user
    bool hidingContent() { return isHidingContent; }
    // Will return true if if system location modified
    bool systemLocationModification() { return isSystemLocationModified; }
    // Will return true if network is being used
    bool networkUsage() { return isNetWorkUsed; }
    // Will return true if other files get executed
    bool otherFileExecuted() { return isOtherFileExcuted; }
    // Will return true if powershell is used
    bool powershellUsage() { return isPowershellUsed; }
    // Will return true if registery used
    bool registeryUsage() { return isRegisteryUsed; }
    // Will return true if task gets terminated
    bool taskTermination() { return isTaskTerminated; }
};

and here is the source implementation,

#include "bat.h"

Bat::Bat(std::wstring fileName)
{
    this->fileName = fileName;
}


std::vector<std::string> Bat::getCommands(std::string line)
{
    std::vector<std::string> commands;
    // This boolean variable helps us to check 
    // if we are looping inside a string literal
    bool isStringLiteralOpened = false;
    std::string currentCommand = "";
    for (char letter : line)
    {
        // remove the trailing white space
        if (currentCommand == "" && letter == ' ')continue;
        else
        {
            // echo "call malware.bat" will not call malware.bat it just prints it
            // we dont want false positives
            if (letter == '"')
            {
                if (isStringLiteralOpened) isStringLiteralOpened = false;
                else isStringLiteralOpened = true;
            }
            if (letter == '&' || letter == '|')
            {
                if (isStringLiteralOpened==false)
                {
                    if(currentCommand.size()>0)commands.push_back(currentCommand);
                    currentCommand = "";
                }
            }
            else
            {
                currentCommand += letter;
            }
        }
    }
    if (currentCommand.size()>0)commands.push_back(currentCommand);
    return commands;
}

void Bat::analyseCommand(unsigned int lineNo, std::string command)
{
    // Check for the attribute changes
    std::string result = "";
    // Regex Patterns
    std::string unsafeExtensions = "(?:\.bat|\.exe|\.cmd|\.pif|\.vb|\.vbs)";
    std::string systemFileLocations = "C:.(?:Windows|Program)";
    std::string attributesPattern = "^attrib";
    std::string deleteContentPattern = "^(?:del|erase)";
    std::string encodingAndDecoding = "^certutil -(?:encode|decode)";
    std::string newDirectoriesPattern = "^mkdir";
    std::string downloadFilePatterns[] = {
        "(New-Object Net\.WebClient)\.DownloadFile",
        "Invoke-WebRequest",
        "^bitsadmin.exe"
    };
    std::string networkPattern = "^(?:netstat|net|netsh)";
    std::string callPattern = "^call";
    std::string registeryPattern = "^reg (?:query|add|delete|copy|save|restore|load|unload|compare|export|import|flags)";
    std::string powershellPattern = "^powershell";
    std::string taskKillPattern = "^tskill";


    // Check for attribute changes
    if (isRegexMatched(command, attributesPattern))
    {
        isAttributesChanged = true;
        result = result + "Attributes are used. ";
        if (command.find("+h") || command.find("+H") != std::string::npos)
        {
            result += "Hiding a file ";
            isHidingContent = true;
            // Let us see whether it is hiding dangerous file extensions
            if (isRegexMatched(command, unsafeExtensions))
            {
                result += "which seems to be malicious";
            }
        }
    }
    // Now check for deletion of contents
    else if(isRegexMatched(command, deleteContentPattern))
    {
        isContentDeleted = true;
        result += "Will delete file(s) ";
        if (isRegexMatched(command, systemFileLocations))
        {
            isSystemLocationModified = true;
            result += "The file present in the system location. ";
        }
        if (command.find("*.*") != std::string::npos)
        {
            result += "Warning! Multiple contents will be deleted ";
        }
    }
    // check whether the bat decodes or encodes something
    else if (isRegexMatched(command, encodingAndDecoding))
    {
        isDecodingOrEncodingOtherFile = true;
        result += "Mailicous behaviour, encodes or decodes other files ";
        // Let us see whether it is hiding dangerous file extensions
        if (isRegexMatched(command, unsafeExtensions))
        {
            result += "Warning! the program will decode/encode an executable file";
        }
    }
    // Check if it creates new directories
    else if (isRegexMatched(command, newDirectoriesPattern))
    {
        result += "New directories will be created ";
        isDirectoriesCreated = true;
        if (isRegexMatched(command, systemFileLocations))
        {
            isSystemLocationModified = true;
            result += "in the system location.";
        }
    }
    // Check if donwloads something from the internet!
    else if (isRegexMatched(command, downloadFilePatterns[0]) || isRegexMatched(command, downloadFilePatterns[1]) || isRegexMatched(command, downloadFilePatterns[2]))
    {
        isFileDownloaded = true;
        result += "Warning! Files will get downloaded from the internet";
    }
    // Now check for network settings to get utilizd
    else if (isRegexMatched(command, networkPattern))
    {
        isNetWorkUsed = true;
        result += "It uses network features ";
        if (isRegexMatched(command, "firewall"))
        {
            isFirewallGetsAccessed = true;
            result += "Warning! It may use/modify your firewall";
        }
    }
    // See if it calls other bat files
    else if (isRegexMatched(command, callPattern))
    {
        isOtherFileExcuted = true;
        result += "Warning! It will execute another bat file";
    }
    // See if it is using the registery
    else if (isRegexMatched(command, registeryPattern))
    {
        isRegisteryUsed = true;
        result += "Registery is used";
    }
    // Check if it uses powershell
    else if (isRegexMatched(command, powershellPattern))
    {
        isPowershellUsed = true;
        result += "Warning! It will use the powershell";
    }
    // Check if it kills other process
    else if (isRegexMatched(command, taskKillPattern))
    {
        isTaskTerminated = true;
        result += "This will terminate several tasks";
    }

    // finally add the result
    if (result.size() > 0)
    {
        //std::cout << result << "\n";
        description.insert(std::pair<unsigned int, std::string>(lineNo, result));
    }
}

bool Bat::isRegexMatched(std::string code, std::string pattern)
{
    try {
        std::regex regexPattern(pattern, std::regex_constants::icase);
        if (regex_search(code, regexPattern))
        {
            return true;
        }
    }
    catch (std::exception &e)
    {
        std::cout << pattern << "\n";
        std::cout << e.what();
    }
    return false;
}

bool Bat::analyseFile()
{
    std::ifstream file(fileName);
    if (file.is_open())
    {
        unsigned int lineNo = 1;
        while (!file.eof())
        {
            std::string line;
            std::getline(file, line);
            std::vector<std::string> individualCommand = getCommands(line);
            // Iterator
            typedef std::vector<std::string>::iterator itr;
            itr i1 = individualCommand.begin();
            itr i2 = individualCommand.end();
            for (itr i = i1; i != i2; ++i)
            {
                //std::cout << *i << "\n";
                analyseCommand(lineNo, *i);
            }
            lineNo++;
        }
        file.close();
        return true;
    }
    else {
        return false;
    }
}

Lucky me I have got a real malware from a virus repository and tested the program and the output is attached below, enter image description here

I would like to know some reviews about this code, is my implementation correct, some comments to improve the code to make it better etc.,

\$\endgroup\$
  • \$\begingroup\$ What if the attacker's .bat file only triggers a python (or any other ...) script that is malicious ? Your "analyser" probably has no clue whether it might be malicious or not. \$\endgroup\$ – Theophile Dano Dec 19 '17 at 10:22
  • \$\begingroup\$ @TheophileDano, I actually thought that but not all computers have python installed in it. Anyways it is nice advice which I will take it and add it to the code \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 19 '17 at 10:26
  • \$\begingroup\$ What if an attacker does SET foo=some-malicious-program and then invokes it usingfoo and not some-malicious-program ? None of your regexp will match this. \$\endgroup\$ – Theophile Dano Dec 19 '17 at 10:28
  • 1
    \$\begingroup\$ Or creates an alias. Your code seems to only match very particular cases which are so obvious that any good malware would pass your analyser without a blink. Moreover, any bat file from games or w/e would possibly pass as "malicious" with your software which is unintended behavior. I would suggest to create deeper analysis of the bat file. Maybe create a Lexer/Parser and then analyze the generated Abstract Syntax Tree and see if any patterns seems malicious. \$\endgroup\$ – Theophile Dano Dec 19 '17 at 10:31
  • \$\begingroup\$ @TheophileDano Thank you for your kind suggestion much appreciated! \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 19 '17 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.