vmray

Malware analysis sandboxing.

Forensics & Malware Analysis · VMRay Analyzer

Details

IDvmray
ProviderVMRay
CategoryForensics & Malware Analysis
From Version5.0.0
Docker Imagedemisto/python3:3.12.12.5490952
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

VMRay XSOAR Integration

This integration enables users to design playbooks that involve analyzing a file in VMRay and retrieving the analysis results and associated threat intelligence.

The Playbooks accelerate incident response and make security operations more scalable and efficient.

Configure VMRay in Cortex

Parameter Description Required
Source Reliability Reliability of the source providing the intelligence data. False
Server URL (e.g., https://cloud.vmray.com)   True
API Key (Recommended)   False
Use system proxy settings   False
Trust any certificate (not secure)   False
Retry requests when API is rate limited   False
API Key (Deprecated) Use the “API Key (Recommended)” parameter instead. False

Known Limitations

  • Non-ASCII characters in file names will be ignored when uploading.

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

vmray-upload-sample


Submits a sample to VMRay for analysis.

Base Command

vmray-upload-sample

Input

Argument Name Description Required
entry_id Entry ID of the file to submit. Required
document_password Password of the document. Optional
archive_password Password of an archive. Optional
sample_type Force type of the file. Optional
shareable Whether the file is shareable. Possible values are: true, false. Optional
max_jobs Maximum number of jobs to create (number). Default is 1. Optional
tags A CSV list of tags to add to the sample. Optional
reanalyze Deprecated. Analyze even if analyses already exist. To control analysis caching, use the API Key settings instead, which are available via the Analysis Settings page, in the VMRay Web Interface. Possible values are: true, false. Optional
net_scheme_name The network scheme to use. Optional

Context Output

Path Type Description
VMRay.Job.JobID Number ID of a new job
VMRay.Job.Created Date Timestamp of job creation.
VMRay.Job.SampleID Number ID of the sample.
VMRay.Job.VMName String Name of the virtual machine.
VMRay.Job.VMID Number ID of the virtual machine.
VMRay.Sample.SampleID Number ID of the sample.
VMRay.Sample.SampleURL String URL to sample page.
VMRay.Sample.Created Date Timestamp of sample creation.
VMRay.Submission.SubmissionID Number Submission ID.
VMRay.Submission.SubmissionURL String URL to submission page.

Command Example

vmray-upload-sample entry_id=79@4 max_jobs=1

Context Example

{
    "VMRay.Sample": [
        {
            "SHA1": "69df095557346b3c136db4378afd5ee7a4839dcc",
            "Created": "2019-05-27T07:48:11",
            "SampleID": 3902285,
            "SampleURL": "https://cloud.vmray.com/user/sample/view?id=3902285",
            "FileName": "KeePass-2.41-Setup.exe",
            "FileSize": 3301376,
            "SSDeep": "98304:rk/6KPcsSO9iShSf0UTsj+te5NrYWM+40n3vGJyc:rkCK0UhSfHsKw5z4OvGJL"
        }
    ],
    "VMRay.Submission": [
        {
            "SampleID": 3902285,
            "SubmissionID": 4569315,
            "SubmissionURL": "https://cloud.vmray.com/user/sample/view?id=3902285"
        }
    ],
    "VMRay.Job": [
        {
            "Created": "2019-05-27T07:48:11",
            "JobRuleSampleType": "Windows PE (x86)",
            "VMID": 20,
            "SampleID": 3902285,
            "JobID": 3908304,
            "VMName": "win10_64_th2"
        }
    ]
}

Human Readable Output

File submitted to VMRay

Jobs ID Samples ID Submissions ID Sample URL
3908304 3902285 4569315 https://cloud.vmray.com/user/sample/view?id=3902285

vmray-upload-url


Submits a URL for analysis.

Base Command

vmray-upload-url

Input

Argument Name Description Required
url The URL to analyze. For example: https://demisto.com. . Required
shareable Whether the analysis is shareable. Possible values are: true, false. Optional
max_jobs Maximum number of jobs to create (number). Default is 1. Optional
tags A CSV list of tags to add to the sample. Optional
net_scheme_name The network scheme to use. Optional

Context Output

Path Type Description
VMRay.Job.JobID Number ID of a new job
VMRay.Job.Created Date Timestamp of job creation.
VMRay.Job.SampleID Number ID of the sample.
VMRay.Job.VMName String Name of the virtual machine.
VMRay.Job.VMID Number ID of the virtual machine.
VMRay.Sample.SampleID Number ID of the sample.
VMRay.Sample.SampleURL String URL to sample page.
VMRay.Sample.Created Date Timestamp of sample creation.
VMRay.Submission.SubmissionID Number Submission ID.
VMRay.Submission.SubmissionURL String URL to submission page.

Command Example

{
    "VMRay.Sample": [
        {
            "SHA1": "884a2738124be5dae95e685fb8c919b1460734c5",
            "Created": "2019-05-27T07:48:11",
            "SampleID": 3902285,
            "SampleURL": "https://cloud.vmray.com/user/sample/view?id=3902285",
            "FileName": "https://demisto.com",
            "FileSize": 20,
            "SSDeep": "3:N8W2K:2W2K"
        }
    ],
    "VMRay.Submission": [
        {
            "SampleID": 3902285,
            "SubmissionID": 4569315,
            "SubmissionURL": "https://cloud.vmray.com/user/sample/view?id=3902285"
        }
    ],
    "VMRay.Job": [
        {
            "Created": "2019-05-27T07:48:11",
            "JobRuleSampleType": "Windows PE (x86)",
            "VMID": 20,
            "SampleID": 3902285,
            "JobID": 3908304,
            "VMName": "win10_64_th2"
        }
    ]
}

Human Readable Output

URL submitted to VMRay

Jobs ID Samples ID Submissions ID
3908304 3902285 4569315

vmray-get-analysis-by-sample


Retrieves all analysis details for a specified sample.

Base Command

vmray-get-analysis-by-sample

Input

Argument Name Description Required
sample_id Sample ID. Required
limit Maximum number of results to return (number). Optional

Context Output

Path Type Description
VMRay.Analysis.AnalysisID Number Analysis ID.
VMRay.Analysis.AnalysisURL String URL to analysis page.
VMRay.Analysis.SampleID Number Sample ID in the analysis.
VMRay.Analysis.Verdict String Verdict for the sample (Malicious, Suspicious, Clean, Not Available).
VMRay.Analysis.VerdictReason String Description of the Verdict Reason.
VMRay.Analysis.Severity String Severity of the sample in the submission (Malicious, Suspicious, Good, Blacklisted, Whitelisted, Unknown). Deprecated.
VMRay.Analysis.JobCreated Date Date when the analysis job started.
VMRay.Analysis.MD5 String MD5 hash of the sample.
VMRay.Analysis.SHA1 String SHA1 hash of the sample.
VMRay.Analysis.SHA256 String SHA256 hash of the sample.
VMRay.Analysis.SSDeep String ssdeep hash of the sample.

Command Example

!vmray-get-analysis-by-sample sample_id=3902238

Context Example

{
    "VMRay.Analysis": [
        {
            "SampleID": 3902238,
            "SampleURL": "https://cloud.vmray.com/user/sample/view?id=3902238",
            "SHA1": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
            "SHA256": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
            "JobCreated": "2021-06-14T12:17:07",
            "AnalysisID": 2779353,
            "Verdict": "Suspicious",
            "VerdictReason": null,
            "Severity": "Suspicious",
            "MD5": "2e0499dc90c2d715a53e05b1890e0442"
        }
    ]
}

Human Readable Output

Analysis results from VMRay for ID 3902238:

AnalysisID SampleID Verdict AnalysisURL
2779353 3902238 Suspicious https://cloud.vmray.com/user/sample/view?id=3902238

vmray-get-job-by-sample


Retrieves details for all jobs for a specified sample.

Base Command

vmray-get-job-by-sample

Input

Argument Name Description Required
sample_id Sample ID. Required

Context Output

Path Type Description
VMRay.Job.JobID Number ID of the job.
VMRay.Job.SampleID Number Sample ID of the job.
VMRay.Job.SubmissionID Number ID of the submission.
VMRay.Job.MD5 String MD5 hash of the sample in the job.
VMRay.Job.SHA1 String SHA1 hash of the sample in the job.
VMRay.Job.SHA256 String SHA256 hash of the sample in the job.
VMRay.Job.SSDeep String ssdeep hash of the sample in the job.
VMRay.Job.VMName String Name of the virtual machine.
VMRay.Job.VMID Number ID of the virtual machine.
VMRay.Job.Status String Status of the job.

Command Example

!vmray-get-job-by-sample sample_id=3902238

Context Example

{
    "VMRay.Job": [
        {
            "VMName": "win7_32_sp1",
            "SampleID": 3902238,
            "SHA1": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
            "SubmissionID": 18950,
            "SHA256": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
            "SSDeep": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
            "Status": "inwork",
            "VMID": 3,
            "JobID": 29208,
            "MD5": "2e0499dc90c2d715a53e05b1890e0442"
        }
    ]
}

Human Readable Output

Job results for sample id: 3902238

JobID SampleID VMName VMID
29208 3902238 win7_32_sp1 3

vmray-get-submission


Retrieves the results of a submission.

Base Command

vmray-get-submission

Input

Argument Name Description Required
submission_id ID of the submission. Can be obtained by running the ‘vmray-upload-sample’ or ‘vmray-upload-url’ command. Required

Context Output

Path Type Description
VMRay.Submission.IsFinished Boolean Whether the submission is finished (true or false).
VMRay.Submission.HasErrors Boolean Whether there are any errors in the submission (true or false).
VMRay.Submission.SubmissionID Number ID of the sample in the submission.
VMRay.Submission.SubmissionURL String URL of submission page.
VMRay.Submission.MD5 String MD5 hash of the sample in the submission.
VMRay.Submission.SHA1 String SHA1 hash of the sample in the submission.
VMRay.Submission.SHA256 String SHA256 hash of the sample in the submission.
VMRay.Submission.SSDeep String ssdeep hash of the sample in the submission.
VMRay.Submission.Verdict String Verdict for the sample (Malicious, Suspicious, Clean, Not Available).
VMRay.Submission.VerdictReason String Description of the Verdict Reason.
VMRay.Submission.Severity String Severity of the sample in the submission (Malicious, Suspicious, Good, Blacklisted, Whitelisted, Unknown). Deprecated.
VMRay.Submission.SampleID Number ID of the sample in the submission.

Command Example

vmray-get-submission submission_id=4569262

Context Example

{
    "DBotScore": [
        {
            "Indicator": "2e0499dc90c2d715a53e05b1890e0442",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        }
    ],
    "VMRay.Submission": {
        "SampleID": 3902238,
        "SHA1": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
        "HasErrors": true,
        "SubmissionID": 4569262,
        "SubmissionURL": "https://cloud.vmray.com/user/sample/view?id=3902238",
        "SHA256": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
        "SSDeep": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
        "Verdict": "Malicious",
        "IsFinished": true,
        "VerdictReason": null,
        "Severity": "Malicious",
        "MD5": "2e0499dc90c2d715a53e05b1890e0442"
    }
}

Human Readable Output

Submission results from VMRay for ID 3902238 with verdict of Malicious

Attribute Value
IsFinished true
Verdict Malicious
HasErrors true
MD5 2e0499dc90c2d715a53e05b1890e0442
SHA1 868a53c394f29f8d3aac7b0a20a371999045b6ed
SHA256 b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66
SSDeep 1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH
SubmissionURL https://cloud.vmray.com/user/sample/view?id=3902238

vmray-get-sample


Retrieves a sample using the sample ID.

Base Command

vmray-get-sample

Input

Argument Name Description Required
sample_id ID of the sample. Required

Context Output

Path Type Description
VMRay.Sample.SampleID Number ID of the sample.
VMRay.Sample.SampleURL String URL to sample page.
VMRay.Sample.FileName String File name of the sample.
VMRay.Sample.MD5 String MD5 hash of the sample.
VMRay.Sample.SHA1 String SHA1 hash of the sample.
VMRay.Sample.SHA256 String SHA256 hash of the sample.
VMRay.Sample.SSDeep String ssdeep hash of the sample.
VMRay.Sample.Verdict String Verdict for the sample (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.Severity String Severity of the sample in the submission (Malicious, Suspicious, Good, Blacklisted, Whitelisted, Unknown). Deprecated.
VMRay.Sample.Type String File type.
VMRay.Sample.Created Date Timestamp of sample creation.
VMRay.Sample.Classifications String Classifications of the sample.
VMRay.Sample.ChildSampleIDs Number List of child sample IDs.
VMRay.Sample.ParentSampleIDs Number List of parent sample IDs.
VMRay.Sample.URL String The sample URL.

Command Example

!vmray-get-sample sample_id=3902238

Context Example

{
    "DBotScore": [
        {
            "Indicator": "2e0499dc90c2d715a53e05b1890e0442",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        }
    ],
    "VMRay.Sample": {
        "SampleID": 3902238,
        "SampleURL": "https://cloud.vmray.com/user/sample/view?id=3902238",
        "SHA1": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
        "Classification": [],
        "SHA256": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
        "FileName": "pafish.exe",
        "Created": "2018-03-20T15:06:49",
        "SSDeep": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
        "Verdict": "Malicious",
        "Type": "Windows Exe (x86-32)",
        "VerdictReason": null,
        "Severity": "Malicious",
        "MD5": "2e0499dc90c2d715a53e05b1890e0442",
        "ChildSampleIDs": [20, 21, 22],
        "ParentSampleIDs": [18],
        "URL": null
    }
}

Human Readable Output

Results for sample id: 3902238 with verdict Malicious

Attribute Value
FileName pafish.exe
Type Windows Exe (x86-32)
MD5 2e0499dc90c2d715a53e05b1890e0442
SHA1 868a53c394f29f8d3aac7b0a20a371999045b6ed
SHA256 b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66
SSDeep 1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH
SampleURL https://cloud.vmray.com/user/sample/view?id=3902238

vmray-get-sample-by-hash


Retrieves sample information by hash.

Base Command

vmray-get-sample-by-hash

Input

Argument Name Description Required
hash MD5, SHA1 or SHA256 hash of the sample. Required

Context Output

Path Type Description
File.Name String The full file name (including file extension).
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.SSDeep String The SSDeep hash of the file.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
VMRay.Sample.SampleID Number ID of the sample.
VMRay.Sample.SampleURL String URL to sample page.
VMRay.Sample.FileName String File name of the sample.
VMRay.Sample.MD5 String MD5 hash of the sample.
VMRay.Sample.SHA1 String SHA1 hash of the sample.
VMRay.Sample.SHA256 String SHA256 hash of the sample.
VMRay.Sample.SSDeep String ssdeep hash of the sample.
VMRay.Sample.Verdict String Verdict for the sample (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.Severity String Severity of the sample in the submission (Malicious, Suspicious, Good, Blacklisted, Whitelisted, Unknown). Deprecated.
VMRay.Sample.Type String File type.
VMRay.Sample.Created Date Timestamp of sample creation.
VMRay.Sample.Classifications String Classifications of the sample.
VMRay.Sample.ChildSampleIDs Number List of child sample IDs.
VMRay.Sample.ParentSampleIDs Number List of parent sample IDs.

Command Example

!vmray-get-sample-by-hash hash=124f46228d1e220d88ae5e9a24d6e713039a64f9

Context Example

{
    "DBotScore": [
        {
        "Indicator": "9159edb64c4a21d8888d088bf2db23f3",
        "Score": 3,
        "Type": "hash",
        "Vendor": "VMRay"
        },
        {
            "Indicator": "2180f4a13add5e346e8cf6994876a9d2f5eac3fcb695db8569537010d24cd6d5",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "124f46228d1e220d88ae5e9a24d6e713039a64f9",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        },
        {
            "Indicator": "1536:tI05L48IVDAQVzZpJyrOM1GhFNkYL2BxNRj:tI05LBIDAuztyrOMGTkrNRj",
            "Score": 3,
            "Type": "hash",
            "Vendor": "VMRay"
        }
    ],
    "VMRay.Sample": [
        {
            "ParentSampleIDs": [],
            "SampleID": 6822,
            "SHA1": "124f46228d1e220d88ae5e9a24d6e713039a64f9",
            "SampleURL": "https://cloud.vmray.com/user/sample/view?id=6822",
            "Classification": [],
            "SHA256": "2180f4a13add5e346e8cf6994876a9d2f5eac3fcb695db8569537010d24cd6d5",
            "FileName": "pafish.exe",
            "Created": "2021-06-24T15:06:04",
            "SSDeep": "1536:tI05L48IVDAQVzZpJyrOM1GhFNkYL2BxNRj:tI05LBIDAuztyrOMGTkrNRj",
            "ChildSampleIDs": [],
            "Verdict": "Malicious",
            "Type": "Windows Exe (x86-32)",
            "VerdictReason": null,
            "Severity": "Malicious",
            "MD5": "9159edb64c4a21d8888d088bf2db23f3"
        }
    ]
}

Human Readable Output

Results for sha1 hash 124f46228d1e220d88ae5e9a24d6e713039a64f9:

Attribute Value
SampleID 5948
FileName pafish.exe
Type Windows Exe (x86-32)
Verdict Malicious
SampleURL https://cloud.vmray.com/user/sample/view?id=5948

vmray-get-threat-indicators


Retrieves threat indicators (VTI).

Base Command

vmray-get-threat-indicators

Input

Argument Name Description Required
sample_id ID of the sample. Can be obtained from the ‘VMRay.Sample.ID’ output. Required

Context Output

Path Type Description
VMRay.ThreatIndicator.AnalysisID Number List of connected analysis IDs.
VMRay.ThreatIndicator.Category String Category of threat indicators.
VMRay.ThreatIndicator.Classification String Classifications of threat indicators.
VMRay.ThreatIndicator.ID Number ID of a threat indicator.
VMRay.ThreatIndicator.Operation String Operation the indicators caused.

Command Example

!vmray-get-threat-indicators sample_id=3902238

Context Output

Omitted for brevity.

Human Readable Output

Omitted for brevity.

vmray-add-tag


Adds a tag to an analysis and/or a submission.

Base Command

vmray-add-tag

Input

Argument Name Description Required
submission_id ID of the submission to which to add tags. Optional
analysis_id ID of the analysis from which to add tags. Optional
tag Tag to add. Optional

Context Output

There is no context output for this command.

Command Example

!vmray-add-tag submission_id=4569262 tag=faulty

Human Readable Output

Tags: faulty has been added to submission: 4569262

vmray-delete-tag


Deletes tags from an analysis and/or a submission.

Base Command

vmray-delete-tag

Input

Argument Name Description Required
analysis_id ID of the analysis from which to delete a tag. Optional
submission_id ID of the submission from which to delete a tag. Optional
tag Tag to delete. Optional

Context Output

There is no context output for this command.

Command Example

!vmray-delete-tag submission_id=4569262 tag=faulty

Human Readable Output

Tags: faulty has been removed from submission: 4569262

vmray-get-iocs


Retrieves Indicators of Compromise for a specified sample.

Base Command

vmray-get-iocs

Input

Argument Name Description Required
sample_id ID of the sample. Required
all_artifacts Whether all artifacts should be returned or only Indicators of Compromise. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
Domain.Name String The domain name
IP.Address String IP address
URL.Data String The URL
Email.Address String The Email address
File.Path String The full file path.
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.SSDeep String The SSDeep hash of the file.
VMRay.Sample.IOC.Domain.AnalysisID Number IDs of other analyses that contain the domain.
VMRay.Sample.IOC.Domain.Countries String Countries associated with the domain.
VMRay.Sample.IOC.Domain.CountryCodes String ISO 3166-1 two-letter country codes associated with the domain.
VMRay.Sample.IOC.Domain.Domain String Domain.
VMRay.Sample.IOC.Domain.ID Number ID of the domain. (deprecated; is always 0)
VMRay.Sample.IOC.Domain.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Domain.IOCType String Type of IOC.
VMRay.Sample.IOC.Domain.IpAddresses String IP addresses associated with the domain.
VMRay.Sample.IOC.Domain.OriginalDomains String Original domains associated with the domain.
VMRay.Sample.IOC.Domain.ParentProcesses String Full commandline of processes where the domain was used.
VMRay.Sample.IOC.Domain.ParentProcessesNames String Names of processes where the domain was used.
VMRay.Sample.IOC.Domain.Protocols String The protocols used for the domain in a request.
VMRay.Sample.IOC.Domain.Sources String The sources where the domain was obtained from.
VMRay.Sample.IOC.Domain.Type String Type of domain.
VMRay.Sample.IOC.Domain.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Domain.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.EmailAddress.AnalysisID Number IDs of other analyses that contain the email address.
VMRay.Sample.IOC.EmailAddress.Classifications String The classifications of the email address.
VMRay.Sample.IOC.EmailAddress.EmailAddress String The email address.
VMRay.Sample.IOC.EmailAddress.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.EmailAddress.IsRecipient Boolean Indicates whether this email address was used as a recipient email.
VMRay.Sample.IOC.EmailAddress.IsSender Boolean Indicates whether this email address was used as a sender email.
VMRay.Sample.IOC.EmailAddress.IOCType String Type of IOC.
VMRay.Sample.IOC.EmailAddress.Subjects String Email subjects this email address was used in.
VMRay.Sample.IOC.EmailAddress.ThreatNames String The threat names of the email address.
VMRay.Sample.IOC.EmailAddress.Type String Type of email address.
VMRay.Sample.IOC.EmailAddress.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.EmailAddress.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.Email.AnalysisID Number IDs of other analyses that contain the email.
VMRay.Sample.IOC.Email.AttachmentTypes String MIME types of attachments found in this email.
VMRay.Sample.IOC.Email.Classifications String The classifications of the email.
VMRay.Sample.IOC.Email.Hashes.MD5 String MD5 of given email.
VMRay.Sample.IOC.Email.Hashes.SSDeep String SSDeep of given email.
VMRay.Sample.IOC.Email.Hashes.SHA256 String SHA256 of given email.
VMRay.Sample.IOC.Email.Hashes.SHA1 String SHA1 of given email.
VMRay.Sample.IOC.Email.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Email.IOCType String Type of IOC.
VMRay.Sample.IOC.Email.NrAttachments Number Number of attachments found in the email.
VMRay.Sample.IOC.Email.NrLinks Number Number of links found in the email.
VMRay.Sample.IOC.Email.Recipients String The email recipients.
VMRay.Sample.IOC.Email.Sender String Sender of the email.
VMRay.Sample.IOC.Email.Subject String Subject of the email.
VMRay.Sample.IOC.Email.ThreatNames String The threat names of the email.
VMRay.Sample.IOC.Email.Type String Type of email.
VMRay.Sample.IOC.Email.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Email.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.Filename.AnalysisID Number IDs of other analyses that contain the filename.
VMRay.Sample.IOC.Filename.Categories String The filename categories.
VMRay.Sample.IOC.Filename.Classifications String The classifications of the filename.
VMRay.Sample.IOC.Filename.Filename String The filename.
VMRay.Sample.IOC.Filename.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Filename.IOCType String Type of IOC.
VMRay.Sample.IOC.Filename.Operations String The filename operations that were performed, e.g., access, create, read, write, and delete.
VMRay.Sample.IOC.Filename.ThreatNames String The threat names of the filename.
VMRay.Sample.IOC.Filename.Type String Type of filename.
VMRay.Sample.IOC.Filename.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Filename.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.File.AnalysisID Number IDs of other analyses that contain the file.
VMRay.Sample.IOC.File.Categories String The file categories.
VMRay.Sample.IOC.File.Classifications String The classifications of the file.
VMRay.Sample.IOC.File.FileSize Number The original size of the file in bytes.
VMRay.Sample.IOC.File.Filename String Name of the file.
VMRay.Sample.IOC.File.Filenames String All known names of the file.
VMRay.Sample.IOC.File.Hashes.MD5 String MD5 hash of the file.
VMRay.Sample.IOC.File.Hashes.SSDeep String SSDeep hash of the file.
VMRay.Sample.IOC.File.Hashes.SHA256 String SHA256 hash of the file.
VMRay.Sample.IOC.File.Hashes.SHA1 String SHA1 hash of the file.
VMRay.Sample.IOC.File.ID Number ID of the file. (deprecated; is always 0)
VMRay.Sample.IOC.File.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.File.IOCType String Type of IOC.
VMRay.Sample.IOC.File.MIMEType String The MIME type of the file.
VMRay.Sample.IOC.File.Name String Same as Filename.
VMRay.Sample.IOC.File.NormFilename String Normalized name of the file.
VMRay.Sample.IOC.File.Operation String Same as Operations, left in for backwards compatibility.
VMRay.Sample.IOC.File.Operations String The file operations which were performed, e.g., access, create, read, write, and delete.
VMRay.Sample.IOC.File.ParentFiles String Files where this file was contained in.
VMRay.Sample.IOC.File.ParentProcesses String Full commandline of processes where the file was referenced.
VMRay.Sample.IOC.File.ParentProcessesNames String Names of processes where the file was referenced.
VMRay.Sample.IOC.File.ResourceURL String URL of where the file was downloaded.
VMRay.Sample.IOC.File.ThreatNames String The threat names of the file.
VMRay.Sample.IOC.File.Type String Type of file.
VMRay.Sample.IOC.File.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.File.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.IP.AnalysisID Number IDs of other analyses that contain the IP address.
VMRay.Sample.IOC.IP.Countries String Countries associated with the IP address.
VMRay.Sample.IOC.IP.CountryCodes String ISO 3166-1 two-letter country codes associated with the IP address.
VMRay.Sample.IOC.IP.Domains String Domains associated with the IP address.
VMRay.Sample.IOC.IP.IP String The IP address.
VMRay.Sample.IOC.IP.ID Number ID of the IP address. (deprecated; is always 0)
VMRay.Sample.IOC.IP.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.IP.IOCType String Type of IOC.
VMRay.Sample.IOC.IP.Operation String Deprecated, always empty.
VMRay.Sample.IOC.IP.ParentProcesses String Full commandline of processes where the IP address was referenced.
VMRay.Sample.IOC.IP.ParentProcessesNames String Names of processes where the IP address was referenced.
VMRay.Sample.IOC.IP.Protocols String Protocols used in communication with this IP.
VMRay.Sample.IOC.IP.Sources String The sources where the IP address was obtained from.
VMRay.Sample.IOC.IP.Type String Type of IP address.
VMRay.Sample.IOC.IP.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.IP.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.Mutex.AnalysisID Number IDs of other analyses that contain the mutex.
VMRay.Sample.IOC.Mutex.Classifications String The mutex classifications.
VMRay.Sample.IOC.Mutex.ID Number ID of the mutex. (deprecated; is always 0)
VMRay.Sample.IOC.Mutex.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Mutex.IOCType String Type of IOC.
VMRay.Sample.IOC.Mutex.Name String Name of the mutex.
VMRay.Sample.IOC.Mutex.Operation String Same as Operations, left in for backwards compatibility.
VMRay.Sample.IOC.Mutex.Operation String The mutex operations that were performed, e.g., access, create, read, write, and delete.
VMRay.Sample.IOC.Mutex.ParentProcesses String Full commandline of processes where the mutex was used.
VMRay.Sample.IOC.Mutex.ParentProcessesNames Unknown Names of processes where the mutex was used.
VMRay.Sample.IOC.Mutex.ThreatNames String The threat names of the mutex.
VMRay.Sample.IOC.Mutex.Type String Type of mutex.
VMRay.Sample.IOC.Mutex.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Mutex.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.Process.AnalysisID Number IDs of other analyses that contain the process.
VMRay.Sample.IOC.Process.Classifications String The process classifications.
VMRay.Sample.IOC.Process.CmdLine String Command line of the process.
VMRay.Sample.IOC.Process.ImageNames String Names of the process executable.
VMRay.Sample.IOC.Process.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Process.IOCType String Type of IOC.
VMRay.Sample.IOC.Process.ParentProcesses String Full commandline of parent processes.
VMRay.Sample.IOC.Process.ParentProcessesNames String Names of parent processes.
VMRay.Sample.IOC.Process.ProcessNames String Names of the processes.
VMRay.Sample.IOC.Process.ThreatNames String The threat names of the process.
VMRay.Sample.IOC.Process.Type String Type of process.
VMRay.Sample.IOC.Process.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Process.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.Registry.AnalysisID Number IDs of other analyses that contain the registry key.
VMRay.Sample.IOC.Registry.Classifications String The registry key classifications.
VMRay.Sample.IOC.Registry.ID Number ID of the registry key. (deprecated; is always 0)
VMRay.Sample.IOC.Registry.IsIOC Boolean Whether this artifact is an Indicator of Compromise (IOC).
VMRay.Sample.IOC.Registry.IOCType String Type of IOC.
VMRay.Sample.IOC.Registry.Name String The normalized registry key name.
VMRay.Sample.IOC.Registry.Operation String Same as Operations, left in for backwards compatibility.
VMRay.Sample.IOC.Registry.Operation String The registry operations that were performed, e.g., access, create, read, write, and delete.
VMRay.Sample.IOC.Registry.ParentProcesses String Full commandline of processes where the registry key was referenced.
VMRay.Sample.IOC.Registry.ParentProcessesNames String Names of processes where the registry key was referenced.
VMRay.Sample.IOC.Registry.ThreatNames String The threat names of the registry key.
VMRay.Sample.IOC.Registry.Type String Type of registry key.
VMRay.Sample.IOC.Registry.ValueTypes String The registry key value type.
VMRay.Sample.IOC.Registry.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.Registry.VerdictReason String Description of the Verdict Reason.
VMRay.Sample.IOC.URL.AnalysisID Number IDs of other analyses that contain the given URL.
VMRay.Sample.IOC.URL.Categories String The URL categories.
VMRay.Sample.IOC.URL.ContentTypes String Content types associated with the URL.
VMRay.Sample.IOC.URL.Countries String Countries associated with the URL.
VMRay.Sample.IOC.URL.CountryCodes String ISO 3166-1 two-letter country codes associated with the URL.
VMRay.Sample.IOC.URL.ID Number ID of the URL. (deprecated; is always 0)
VMRay.Sample.IOC.URL.IPAddresses String IP addresses associated with the URL.
VMRay.Sample.IOC.URL.Methods String Methods of HTTP requests directed at this URL.
VMRay.Sample.IOC.URL.Operation String Deprecated, always empty.
VMRay.Sample.IOC.URL.OriginalURLs String The origin URLs the malware used in the artifact operation.
VMRay.Sample.IOC.URL.ParentFiles String Names of files where the URL was referenced.
VMRay.Sample.IOC.URL.ParentProcesses String Full commandline of processes where the URL was referenced.
VMRay.Sample.IOC.URL.ParentProcessesNames String Names of processes where the URL was referenced.
VMRay.Sample.IOC.URL.Referrers String Other URLs that referred to this URL.
VMRay.Sample.IOC.URL.Source String The sources where the URL was obtained from.
VMRay.Sample.IOC.URL.Type String Type of the URL.
VMRay.Sample.IOC.URL.URL String The URL.
VMRay.Sample.IOC.URL.UserAgents String User agents used to connect to this URL.
VMRay.Sample.IOC.URL.Verdict String Verdict for the artifact (Malicious, Suspicious, Clean, Not Available).
VMRay.Sample.IOC.URL.VerdictReason String Description of the Verdict Reason.

Command Example

!vmray-get-iocs sample_id=3902238

Context Example

Omitted for brevity.

Human Readable Output

Omitted for brevity.

vmray-get-job-by-id


Retrieves a job by job ID.

Base Command

vmray-get-job-by-id

Input

Argument Name Description Required
job_id ID of a job. Required

Context Output

Path Type Description
VMRay.Job.JobID Number ID of the job.
VMRay.Job.SampleID Number Sample ID of the job.
VMRay.Job.SubmissionID Number ID of the submission.
VMRay.Job.MD5 String MD5 hash of the sample in the job.
VMRay.Job.SHA1 String SHA1 hash of the sample in the job.
VMRay.Job.SHA256 String SHA256 hash of the sample in the job.
VMRay.Job.SSDeep String ssdeep hash of the sample in the job.
VMRay.Job.VMName String Name of the virtual machine.
VMRay.Job.VMID Number ID of the virtual machine.
VMRay.Job.Status String Status of the job.

Command Example

!vmray-get-job-by-id job_id=365547

Context Example

{
    "VMRay.Job": {
        "VMName": "win7_32_sp1",
        "SampleID": 3902238,
        "SHA1": "868a53c394f29f8d3aac7b0a20a371999045b6ed",
        "SubmissionID": 4569262,
        "SHA256": "b8a4b647e56cb71773d0086b51906b902a7ccafe699f4068da4cb5cd234d9d66",
        "SSDeep": "1536:Hg8ktOZtz+PZvpJyrOM1GhFNkYL2BxNRjWW:H/kY0Z3yrOMGTkrNRjH",
        "Status": "inwork",
        "VMID": 3,
        "JobID": 365547,
        "MD5": "2e0499dc90c2d715a53e05b1890e0442"
    }
}

Human Readable Output

Job results for job id: 365547

Attribute Value
JobID 365547
SampleID 3902238
VMName win7_32_sp1
VMID 3

vmray-get-summary


Retrieves the Summary JSON v2 for a specific analysis.

Base Command

vmray-get-summary

Input

Argument Name Description Required
analysis_id ID of the analysis from which to retrieve the Summary JSON v2 from (analysis ID is returned e.g. from vmray-get-analysis-by-sample). Required

Context Output

Path Type Description
InfoFile.Name string Filename
InfoFile.EntryID string The EntryID of the Summary JSON v2
InfoFile.Size number The file size of the Summary JSON v2
InfoFile.Info string MIME type of the Summary JSON v2

Command Example

!vmray-get-summary analysis_id=2779353

Context Example

{
    "InfoFile": {
        "EntryID": "407@21232f297a57a5a743894a0e4a801fc3$&$9c7fe1a0-4045-4b69-8257-08ef3306318a",
        "Extension": "json",
        "Info": "application/json",
        "Name": "summary_v2.json",
        "Size": 37630,
        "Type": "ASCII text, with very long lines"
    }
}

Human Readable Output

Returned file: summary_v2.json Download

vmray-get-screenshots


Retrieves screenshots taken during a specific dynamic analysis. The screenshots are stored with file names like ‘analysis_5_screenshot_2.png’. In this example, ‘5’ represents the analysis ID from which the screenshot came, and ‘2’ indicates that it’s the third screenshot taken during the analysis, in chronological order.

Base Command

vmray-get-screenshots

Input

Argument Name Description Required
analysis_id ID of the analysis from which to retrieve the screenshots from (analysis ID is returned e.g. from vmray-get-analysis-by-sample). Required

Context Output

Path Type Description
InfoFile.Name string Filename
InfoFile.EntryID string The EntryID of the file
InfoFile.Size number The file size of the file
InfoFile.Info string MIME type of the file

Command example

!vmray-get-screenshots analysis_id="50615"

Context Example

{
    "InfoFile": [
        {
            "EntryID": "488@b7d0844f-d230-402a-81de-154dc1c57cc9",
            "Extension": "png",
            "Info": "image/png",
            "Name": "analysis_50615_screenshot_0.png",
            "Size": 753660,
            "Type": "PNG image data, 800 x 600, 8-bit/color RGB, non-interlaced"
        },
        {
            "EntryID": "489@b7d0844f-d230-402a-81de-154dc1c57cc9",
            "Extension": "png",
            "Info": "image/png",
            "Name": "analysis_50615_screenshot_1.png",
            "Size": 412598,
            "Type": "PNG image data, 800 x 600, 8-bit/color RGB, non-interlaced"
        }
    ]
}

vmray-get-license-usage-verdicts


Gets the usage of verdicts from VMRay.

Base Command

vmray-get-license-usage-verdicts

Input

There is no input for this command.

Context Output

Path Type Description
VMRay.VerdictQuota.PeriodEndDate string License end date.
VMRay.VerdictQuota.VerdictQuota number Total number of available verdicts (per month).
VMRay.VerdictQuota.VerdictRemaining number Remaining number of verdicts (per month).
VMRay.VerdictQuota.VerdictUsage number Percentages used.

Command Example


#### Context Example

```json
{
    "VMRay.VerdictQuota": {
        "PeriodEndDate": "2024-02-03 14:12 (UTC+1)",
        "VerdictQuota": 100,
        "VerdictRemaining": 90,
        "VerdictUsage": 10
    }
}

Human Readable Output

VerdictQuota 100
VerdictRemaining 90
VerdictUsage 10.0
PeriodEndDate 2024-02-03 14:12 (UTC+1)

vmray-get-license-usage-reports


Gets the usage of reports from VMRay.

Base Command

vmray-get-license-usage-reports

Input

There is no input for this command.

Context Output

Path Type Description
VMRay.ReportQuota.PeriodEndDate string License end date.
VMRay.ReportQuota.VerdictQuota number Total number of available reports (per month).
VMRay.ReportQuota.VerdictRemaining number Remaining number of reports (per month).
VMRay.ReportQuota.VerdictUsage number Percentages used.

Context Example

{
    "VMRay.ReportsQuota": {
        "PeriodEndDate": "2024-02-03 14:12 (UTC+1)",
        "ReportQuota": 100,
        "ReportRemaining": 90,
        "ReportUsage": 10
    }
}

Command Example

vmray-get-license-usage-reports

Human Readable Output

ReportQuota 100
ReportRemaining 90
ReportUsage 10.0
PeriodEndDate 2024-02-03 14:12 (UTC+1)

vmray-get-pdf-report


Retrieves the PDF report for a specific sample.

Base Command

vmray-get-pdf-report

Input

Argument Name Description Required
sample_id ID of the sample from which to retrieve the PDF report. Required

Context Output

Path Type Description
InfoFile.Name string Filename.
InfoFile.EntryID string The EntryID of the PDF report.
InfoFile.Size number The file size of the PDF report.
InfoFile.Info string MIME type of the PDF report.

Configuration parameters

  • integrationReliability — Source Reliability
  • server — Server URL (e.g., https://cloud.vmray.com) (required)
  • credentials
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • retry_on_rate_limit — Retry requests when API is rate limited
  • api_key — API Key (Deprecated)

Commands (20)

  • get_job_sample Deprecated

    This command performs a function similar to the 'vmray-get-job-by-sample' command.

  • get_results Deprecated

    similar to vmray-get-sample.

  • upload_sample Deprecated

    Retrieves a sample using the sample ID. (Deprecated).

  • vmray-add-tag

    Adds a tag to an analysis and/or a submission.

  • vmray-delete-tag

    Deletes tags from an analysis and/or a submission.

  • vmray-get-analysis-by-sample

    Retrieves all analysis details for a specified sample.

  • vmray-get-iocs

    Retrieves Indicators of Compromise for a specified sample.

  • vmray-get-job-by-id

    Retrieves a job by job ID.

  • vmray-get-job-by-sample

    Retrieves details for all jobs for a specified sample.

  • vmray-get-license-usage-reports

    Get the used quota of reports.

  • vmray-get-license-usage-verdicts

    Get the used quota of verdicts.

  • vmray-get-pdf-report

    Retrieves the PDF report for a specific sample.

  • vmray-get-sample

    Retrieves a sample using the sample ID.

  • vmray-get-sample-by-hash

    Retrieves sample information by hash.

  • vmray-get-screenshots

    Retrieves screenshots taken during a specific dynamic analysis. The screenshots are stored with file names like 'analysis_5_screenshot_2.png'. In this example, '5' represents the analysis ID from which the screenshot came, and '2' indicates that it's the third screenshot taken during the analysis, in chronological order.

  • vmray-get-submission

    Retrieves the results of a submission.

  • vmray-get-summary

    Retrieves the Summary JSON v2 for a specific analysis.

  • vmray-get-threat-indicators

    Retrieves threat indicators (VTI).

  • vmray-upload-sample

    Submits a sample to VMRay for analysis.

  • vmray-upload-url

    Submits a URL for analysis.

import io
import os
from dataclasses import dataclass
from zipfile import ZipFile

import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401

""" GLOBAL PARAMS """
API_KEY = demisto.params().get("api_key") or demisto.params().get("credentials", {}).get("password")
if not API_KEY:
    raise ValueError("The API Key parameter is required.")
SERVER = (
    demisto.params()["server"][:-1]
    if (demisto.params()["server"] and demisto.params()["server"].endswith("/"))
    else demisto.params()["server"]
)
RETRY_ON_RATE_LIMIT = demisto.params().get("retry_on_rate_limit", True)
SERVER += "/rest/"
USE_SSL = not demisto.params().get("insecure", False)
PROXY = demisto.params().get("proxy", True)
HEADERS = {"Authorization": f"api_key {API_KEY}", "User-Agent": "Cortex XSOAR/1.1.11"}
ERROR_FORMAT = "Error in API call to VMRay [{}] - {}"
RELIABILITY = demisto.params().get("integrationReliability", DBotScoreReliability.C) or DBotScoreReliability.C
INDEX_LOG_DELIMITER = "|"
VENDOR_NAME = "vmray"

# Disable insecure warnings
urllib3.disable_warnings()

""" HELPER DICTS """
SEVERITY_DICT = {
    "malicious": "Malicious",
    "suspicious": "Suspicious",
    "not_suspicious": "Good",
    "blacklisted": "Blacklisted",
    "whitelisted": "Whitelisted",
    "unknown": "Unknown",
    None: "Unknown",
}

VERDICT_DICT = {
    "malicious": "Malicious",
    "suspicious": "Suspicious",
    "clean": "Benign",
    "not_available": "Not Available",
    None: "Not Available",
}

DBOTSCORE = {
    "Malicious": 3,
    "Suspicious": 2,
    "Clean": 1,
    "Benign": 1,
    "Not Available": 0,
}

RATE_LIMIT_REACHED = 429
MAX_RETRIES = 10

""" HELPER CLASSES """


@dataclass
class ScreenshotLogEntry:
    timestamp: int
    file_size: int
    md5: str
    sha1: str
    sha256: str
    filename: str

    @classmethod
    def parse(cls, line: bytes) -> "ScreenshotLogEntry":
        """Parse a log line and return a ScreenshotLogEntry instance."""
        parts = [part.strip() for part in line.decode("utf-8", errors="replace").split(INDEX_LOG_DELIMITER)]

        if len(parts) != 4:
            raise ValueError(f"Expected 4 parts separated by `{INDEX_LOG_DELIMITER}`, got {len(parts)}")

        timestamp = int(parts[0])
        file_size = int(parts[1])
        filename = parts[3]

        # Parse the hash string
        hashes = {}
        for hash_pair in parts[2].split(","):
            key, value = hash_pair.split("=")
            hashes[key] = value

        return cls(
            timestamp=timestamp,
            file_size=file_size,
            md5=hashes["md5"],
            sha1=hashes["sha1"],
            sha256=hashes["sha256"],
            filename=filename,
        )


""" HELPER FUNCTIONS """


def is_json(response):
    """Checks if response is jsonable

    Args:
        response (requests.Response):

    Returns:
        bool: true if object is jsonable
    """
    try:
        response.json()
    except ValueError:
        return False
    return True


def check_id(id_to_check: int | str) -> bool:
    """Checks if parameter id_to_check is a number

    Args:
        id_to_check (int or str):

    Returns:
        bool: True if is a number, else returns error
    """
    if isinstance(id_to_check, int) or (isinstance(id_to_check, str) and id_to_check.isdigit()):
        return True
    raise ValueError(f"Invalid ID `{id_to_check}` provided.")


def get_billing_type(analysis_id: int) -> str | None:
    """Try to read the billing type from the analysis."""

    response = http_request("GET", f"analysis/{analysis_id}")
    if (analysis_data := response.get("data")) is not None:
        return analysis_data.get("analysis_billing_type")

    return None


def build_errors_string(errors):
    """

    Args:
        errors (list, dict or unicode):

    Returns:
        str: error message
    """
    if isinstance(errors, str):
        return str(errors)
    elif isinstance(errors, list):
        err_str = ""
        for error in errors:
            err_str += error.get("error_msg") + ".\n"
    else:
        err_str = errors.get("error_msg")
    return err_str


def http_request(method, url_suffix, params=None, files=None, get_raw=False, ignore_errors=False):
    """General HTTP request.
    Args:
        ignore_errors (bool):
        method: (str) 'GET', 'POST', 'DELETE' 'PUT'
        url_suffix: (str)
        params: (dict)
        files: (dict)
        get_raw: (bool) return raw data instead of dict

    Returns:
        dict: response json
    """

    def find_error(may_be_error_inside):
        """Function will search for dict with 'errors' or 'error_msg' key

        Args:
            may_be_error_inside: object, any object

        Returns:
            None if no error presents
            Errors list/string if errors inside.
        """
        if isinstance(may_be_error_inside, list):
            for obj in may_be_error_inside:
                ans = find_error(obj)
                if ans:
                    return ans
            return None
        if isinstance(may_be_error_inside, dict):
            if "error_msg" in may_be_error_inside:
                return may_be_error_inside["error_msg"]
            if "errors" in may_be_error_inside and may_be_error_inside.get("errors"):
                return may_be_error_inside["errors"]
            for value in may_be_error_inside.values():
                err_r = find_error(value)
                if err_r:
                    return err_r
        return None

    def error_handler(res):  # pragma: no cover
        if res.status_code == RATE_LIMIT_REACHED and "Retry-After" in res.headers:
            return_error(f"Rate limit exceeded! Please wait {res.headers.get('Retry-After', 0)} seconds and re-run.")
        if res.status_code in {405, 401}:
            return_error(ERROR_FORMAT.format(res.status_code, "Token may be invalid"))

    try:
        res = generic_http_request(
            method=method,
            server_url=SERVER,
            verify=USE_SSL,
            proxy=PROXY,
            client_headers=HEADERS,
            url_suffix=url_suffix,
            files=files,
            params=params,
            retries=MAX_RETRIES,
            error_handler=error_handler,
            resp_type="response",
            ok_codes=(200, 201, 202, 204),
            status_list_to_retry=[429],
        )

        if not get_raw and not is_json(res):
            raise ValueError
        response = res.json() if not get_raw else res.content
        if res.status_code not in {200, 201, 202, 204} and not ignore_errors:
            if get_raw and isinstance(response, str):
                # this might be json even if get_raw is True because the API will return errors as json
                try:
                    response = json.loads(response)
                except ValueError:
                    pass
            err = find_error(response)
            if not err:
                err = res.text
            return_error(ERROR_FORMAT.format(res.status_code, err))

        err = find_error(response)
        if err:
            if "no jobs were created" in build_errors_string(err):
                err_message = (
                    err[0].get("error_msg") + ". There is a possibility this file has been analyzed "
                    "before. Please change the Analysis Caching mode for this "
                    'API key to something other than "Legacy" in the VMRay '
                    "Web Interface."
                )
                err[0]["error_msg"] = err_message
            return_error(ERROR_FORMAT.format(res.status_code, err))

        return response
    except ValueError:
        # If no JSON is present, must be an error that can't be ignored
        return_error(ERROR_FORMAT.format(res.status_code, res.text))
    except Exception as e:
        return_error(str(e))


def dbot_score_by_hash(data):
    """Gets a dict containing MD5/SHA1/SHA256/SSDeep and return dbotscore

    Args:
        data: (dict)

    Returns:
        list: dbot scores
    """
    hashes = ["MD5", "SHA256", "SHA1", "SSDeep"]
    scores = []
    for hash_type in hashes:
        if hash_type in data:
            scores.append(
                {
                    "Indicator": data.get(hash_type),
                    "Type": "hash",
                    "Vendor": VENDOR_NAME,
                    "Score": DBOTSCORE.get(data.get("Verdict", 0)),
                    "Reliability": RELIABILITY,
                }
            )
    return scores


def build_job_data(data):
    """

    Args:
        data: any kind of object.

    Returns:
        list: list of jobs
    """

    def build_entry(entry_data):
        entry = {}
        entry["JobID"] = entry_data.get("job_id")
        entry["SampleID"] = entry_data.get("job_sample_id")
        entry["SubmissionID"] = entry_data.get("job_submission_id")
        entry["MD5"] = entry_data.get("job_sample_md5")
        entry["SHA1"] = entry_data.get("job_sample_sha1")
        entry["SHA256"] = entry_data.get("job_sample_sha256")
        entry["SSDeep"] = entry_data.get("job_sample_ssdeep")
        entry["VMName"] = entry_data.get("job_vm_name")
        entry["VMID"] = entry_data.get("job_vm_id")
        entry["Status"] = entry_data.get("job_status")
        return entry

    jobs_list = []
    if isinstance(data, list):
        for item in data:
            jobs_list.append(build_entry(item))
    elif isinstance(data, dict):
        jobs_list = build_entry(data)
    return jobs_list


def build_finished_job(job_id, sample_id):
    entry = {}
    entry["JobID"] = job_id
    entry["SampleID"] = sample_id
    entry["Status"] = "Finished/NotExists"
    return entry


def build_analysis_data(analyses):
    """

    Args:
        analyses: (dict) of analysis

    Returns:
        dict: formatted entry context
    """
    entry_context = {}
    entry_context["VMRay.Analysis(val.AnalysisID === obj.AnalysisID)"] = [
        {
            "AnalysisID": analysis.get("analysis_id"),
            "AnalysisURL": analysis.get("analysis_webif_url"),
            "SampleID": analysis.get("analysis_sample_id"),
            "Verdict": VERDICT_DICT.get(analysis.get("analysis_verdict")),
            "VerdictReason": analysis.get("analysis_verdict_reason_description"),
            "Severity": SEVERITY_DICT.get(analysis.get("analysis_severity")),
            "JobCreated": analysis.get("analysis_job_started"),
            "SHA1": analysis.get("analysis_sample_sha1"),
            "MD5": analysis.get("analysis_sample_md5"),
            "SHA256": analysis.get("analysis_sample_sha256"),
        }
        for analysis in analyses
    ]

    scores = []  # type: list
    for analysis in entry_context:
        scores.extend(dbot_score_by_hash(analysis))
    entry_context[outputPaths["dbotscore"]] = scores

    return entry_context


def build_upload_params():
    """Builds params for upload_file

    Returns:
        dict: params
    """
    # additional params
    doc_pass = demisto.args().get("document_password")
    arch_pass = demisto.args().get("archive_password")
    sample_type = demisto.args().get("sample_type")
    shareable = demisto.args().get("shareable")
    max_jobs = demisto.args().get("max_jobs")
    tags = demisto.args().get("tags")
    net_scheme_name = demisto.args().get("net_scheme_name")

    params = {}
    if doc_pass:
        params["document_password"] = doc_pass
    if arch_pass:
        params["archive_password"] = arch_pass
    if sample_type:
        params["sample_type"] = sample_type

    params["shareable"] = shareable == "true"

    if max_jobs:
        if (isinstance(max_jobs, str) and max_jobs.isdigit()) or isinstance(max_jobs, int):
            params["max_jobs"] = int(max_jobs)
        else:
            raise ValueError("max_jobs arguments isn't a number")
    if tags:
        params["tags"] = tags
    if net_scheme_name:
        params["user_config"] = '{"net_scheme_name": "' + str(net_scheme_name) + '"}'
    return params


def test_module():
    """Simple get request to see if connected"""
    response = http_request("GET", "analysis?_limit=1")
    if response.get("result"):
        demisto.results("ok")
    else:
        raise ValueError(f"Can't authenticate: {response}")


def submit(params, files=None):
    """Submit a file/URL to VMRay Platform

    Args:
        params: (dict)
        files: (dict)

    Returns:
        dict: response
    """

    suffix = "sample/submit"
    results = http_request("POST", url_suffix=suffix, params=params, files=files)
    return results


def build_submission_data(raw_response, type_):
    """Process a submission response from VMRay Platform

    Args:
        raw_response: (dict)
        type_: (str)
    """

    data = raw_response.get("data")

    jobs_list = []
    jobs = data.get("jobs", [])
    for job in jobs:
        if isinstance(job, dict):
            job_entry = {}
            job_entry["JobID"] = job.get("job_id")
            job_entry["Created"] = job.get("job_created")
            job_entry["SampleID"] = job.get("job_sample_id")
            job_entry["VMName"] = job.get("job_vm_name")
            job_entry["VMID"] = job.get("job_vm_id")
            job_entry["JobRuleSampleType"] = job.get("job_jobrule_sampletype")
            jobs_list.append(job_entry)

    samples_list = []
    samples = data.get("samples", [])
    for sample in samples:
        if isinstance(sample, dict):
            sample_entry = {}
            sample_entry["SampleID"] = sample.get("sample_id")
            sample_entry["SampleURL"] = sample.get("sample_webif_url")
            sample_entry["Created"] = sample.get("sample_created")
            sample_entry["FileName"] = sample.get("submission_filename")
            sample_entry["FileSize"] = sample.get("sample_filesize")
            sample_entry["SSDeep"] = sample.get("sample_ssdeephash")
            sample_entry["SHA1"] = sample.get("sample_sha1hash")
            samples_list.append(sample_entry)

    submissions_list = []
    submissions = data.get("submissions", [])
    for submission in submissions:
        if isinstance(submission, dict):
            submission_entry = {}
            submission_entry["SubmissionID"] = submission.get("submission_id")
            submission_entry["SubmissionURL"] = submission.get("submission_webif_url")
            submission_entry["SampleID"] = submission.get("submission_sample_id")
            submissions_list.append(submission_entry)

    entry_context = {}
    entry_context["VMRay.Job(val.JobID === obj.JobID)"] = jobs_list
    entry_context["VMRay.Sample(val.SampleID === obj.SampleID)"] = samples_list
    entry_context["VMRay.Submission(val.SubmissionID === obj.SubmissionID)"] = submissions_list

    table = {
        "Jobs ID": [job.get("JobID") for job in jobs_list],
        "Samples ID": [sample.get("SampleID") for sample in samples_list],
        "Submissions ID": [submission.get("SubmissionID") for submission in submissions_list],
        "Sample URL": [sample.get("SampleURL") for sample in samples_list],
    }
    human_readable = tableToMarkdown(
        type_ + " submitted to VMRay",
        t=table,
        headers=["Jobs ID", "Samples ID", "Submissions ID", "Sample URL"],
    )

    return_outputs(readable_output=human_readable, outputs=entry_context, raw_response=raw_response)


def encode_file_name(file_name):
    """
    encodes the file name - i.e ignoring invalid chars and removing backslashes
    Args:
        file_name (str): name of the file
    Returns: encoded file name
    """
    file_name = file_name.translate(dict.fromkeys(map(ord, '<>:"/\\|?*')))
    return file_name.encode("utf-8", "ignore")


def upload_sample_command():
    """Uploads a file to vmray"""
    # Preserve BC
    file_id = demisto.args().get("entry_id") if demisto.args().get("entry_id") else demisto.args().get("file_id")
    params = build_upload_params()

    file_obj = demisto.getFilePath(file_id)
    # Ignoring non ASCII
    file_name = encode_file_name(file_obj["name"])
    file_path = file_obj["path"]
    with open(file_path, "rb") as f:
        files = {"sample_file": (file_name, f)}
        # Request call
        raw_response = submit(params, files=files)
        return build_submission_data(raw_response, "File")


def upload_url_command():
    """upload a URL to VMRay"""
    args = demisto.args()
    url = args.get("url")

    if isinstance(url, str):
        url = str(url)

    params = build_upload_params()
    params["sample_url"] = url
    raw_response = submit(params)

    return build_submission_data(raw_response, "URL")


def get_analysis(sample, params=None):
    """Uploading sample to vmray

    Args:
        sample (str): sample id
        params (dict): dict of params

    Returns:
        dict: response
    """
    suffix = f"analysis/sample/{sample}"
    response = http_request("GET", suffix, params=params)
    return response


def get_analysis_command():
    sample_id = demisto.args().get("sample_id")
    check_id(sample_id)
    limit = demisto.args().get("limit")
    params = {"_limit": limit}
    raw_response = get_analysis(sample_id, params)
    data = raw_response.get("data")
    if data:
        entry_context = build_analysis_data(data)
        human_readable = tableToMarkdown(
            f"Analysis results from VMRay for ID {sample_id}:",
            entry_context.get("VMRay.Analysis(val.AnalysisID === obj.AnalysisID)"),
            headers=["AnalysisID", "SampleID", "Verdict", "AnalysisURL"],
        )
        return_outputs(human_readable, entry_context, raw_response=raw_response)
    else:
        return_outputs(f"#### No analysis found for sample id {sample_id}", None)


def get_submission(submission_id):
    """

    Args:
        submission_id (str): if of submission

    Returns:
        dict: response
    """
    suffix = f"submission/{submission_id}"
    response = http_request("GET", url_suffix=suffix)
    return response


def get_submission_command():
    submission_id = demisto.args().get("submission_id")
    check_id(submission_id)
    demisto.info(f"Getting submission for {submission_id}")

    try:
        raw_response = get_submission(submission_id)
    except Exception as err:
        demisto.error(str(err))
        raise err

    data = raw_response.get("data")
    if data:
        # Build entry
        entry = {}
        entry["IsFinished"] = data.get("submission_finished")
        entry["HasErrors"] = data.get("submission_has_errors")
        entry["SubmissionID"] = data.get("submission_id")
        entry["SubmissionURL"] = data.get("submission_webif_url")
        entry["MD5"] = data.get("submission_sample_md5")
        entry["SHA1"] = data.get("submission_sample_sha1")
        entry["SHA256"] = data.get("submission_sample_sha256")
        entry["SSDeep"] = data.get("submission_sample_ssdeep")
        entry["Verdict"] = VERDICT_DICT.get(data.get("submission_verdict"))
        entry["VerdictReason"] = data.get("submission_verdict_reason_description")
        entry["Severity"] = SEVERITY_DICT.get(data.get("submission_severity"))
        entry["SampleID"] = data.get("submission_sample_id")
        scores = dbot_score_by_hash(entry)

        entry_context = {
            "VMRay.Submission(val.SubmissionID === obj.SubmissionID)": entry,
            outputPaths.get("dbotscore"): scores,
        }

        human_readable = tableToMarkdown(
            "Submission results from VMRay for ID {} with verdict of {}".format(submission_id, entry.get("Verdict", "Unknown")),
            entry,
            headers=[
                "IsFinished",
                "Verdict",
                "HasErrors",
                "MD5",
                "SHA1",
                "SHA256",
                "SSDeep",
                "SubmissionURL",
            ],
        )

        return_outputs(human_readable, entry_context, raw_response=raw_response)
    else:
        return_outputs(
            f"No submission found in VMRay for submission id: {submission_id}",
            {},
        )


def get_sample(sample_id):
    """building http request for get_sample_command

    Args:
        sample_id (str, int):

    Returns:
        dict: data from response
    """
    suffix = f"sample/{sample_id}"
    response = http_request("GET", suffix)
    return response


def create_sample_entry(data):
    """Construct output dict from api response data

    Args:
        data (dict):

    Returns:
        dict: entry

    """
    entry = {}
    entry["SampleID"] = data.get("sample_id")
    entry["SampleURL"] = data.get("sample_webif_url")
    entry["FileName"] = data.get("sample_filename")
    entry["MD5"] = data.get("sample_md5hash")
    entry["SHA1"] = data.get("sample_sha1hash")
    entry["SHA256"] = data.get("sample_sha256hash")
    entry["SSDeep"] = data.get("sample_ssdeephash")
    entry["Verdict"] = VERDICT_DICT.get(data.get("sample_verdict"))
    entry["VerdictReason"] = data.get("sample_verdict_reason_description")
    entry["Severity"] = SEVERITY_DICT.get(data.get("sample_severity"))
    entry["Type"] = data.get("sample_type")
    entry["Created"] = data.get("sample_created")
    entry["Classification"] = data.get("sample_classifications")
    entry["ChildSampleIDs"] = data.get("sample_child_sample_ids")
    entry["ParentSampleIDs"] = data.get("sample_parent_sample_ids")
    entry["URL"] = data.get("sample_url")

    return entry


def get_sample_command():
    sample_id = demisto.args().get("sample_id")
    check_id(sample_id)

    # query API
    raw_response = get_sample(sample_id)

    # build response dict
    data = raw_response.get("data")
    entry = create_sample_entry(data)
    scores = dbot_score_by_hash(entry)
    entry_context = {
        "VMRay.Sample(val.SampleID === obj.SampleID)": entry,
        outputPaths.get("dbotscore"): scores,
    }

    header_type_name = "FileName"
    score = DBOTSCORE.get(entry.get("Verdict", "Not Available"), 0)
    if (url := entry.get("URL")) is not None:
        dbot_score = Common.DBotScore(
            indicator=url,
            indicator_type="url",
            integration_name=VENDOR_NAME,
            score=score,
            reliability=RELIABILITY,
        )
        url_ctx = Common.URL(url=url, dbot_score=dbot_score)
        entry_context.update(url_ctx.to_context())
        header_type_name = "URL"
    else:
        dbot_score = Common.DBotScore(
            indicator=entry["SHA256"],
            indicator_type="file",
            integration_name=VENDOR_NAME,
            score=score,
            reliability=RELIABILITY,
        )
        file_ctx = Common.File(
            dbot_score=dbot_score,
            md5=entry["MD5"],
            sha1=entry["SHA1"],
            sha256=entry["SHA256"],
            ssdeep=entry["SSDeep"],
            name=entry["FileName"],
        )
        entry_context.update(file_ctx.to_context())

    human_readable = tableToMarkdown(
        "Results for sample id: {} with verdict {}".format(entry.get("SampleID"), entry.get("Verdict", "Unknown")),
        entry,
        headers=[header_type_name, "Type", "MD5", "SHA1", "SHA256", "SSDeep", "SampleURL"],
    )
    return_outputs(human_readable, entry_context, raw_response=raw_response)


def get_sample_by_hash(hash_type, hash):
    """building http request for get_sample_by_hash_command

    Args:
        hash_type (str)
        hash (str)

    Returns:
        list[dict]: list of matching samples
    """
    suffix = f"sample/{hash_type}/{hash}"
    response = http_request("GET", suffix)
    return response


def get_sample_by_hash_command():
    hash = demisto.args().get("hash").strip()

    hash_type_lookup = {32: "md5", 40: "sha1", 64: "sha256"}
    hash_type = hash_type_lookup.get(len(hash))
    if hash_type is None:
        error_string = " or ".join(f"{len_} ({type_})" for len_, type_ in hash_type_lookup.items())
        raise ValueError(f"Invalid hash provided, must be of length {error_string}. Provided hash had a length of {len(hash)}.")

    # query API
    raw_response = get_sample_by_hash(hash_type, hash)

    # build response dict
    samples = raw_response.get("data")

    if samples:
        # VMRay outputs
        entry_context = {}
        context_key = f"VMRay.Sample(val.{hash.upper()} === obj.{hash.upper()})"
        entry_context[context_key] = [create_sample_entry(sample) for sample in samples]

        # DBotScore output
        scores = []  # type: list
        for sample in entry_context[context_key]:
            scores += dbot_score_by_hash(sample)
        entry_context[outputPaths["dbotscore"]] = scores

        # Indicator output
        # just use the first sample that is returned by the API for now
        entry = entry_context[context_key][0]
        file = Common.File(
            None, md5=entry["MD5"], sha1=entry["SHA1"], sha256=entry["SHA256"], ssdeep=entry["SSDeep"], name=entry["FileName"]
        )
        entry_context.update(file.to_context())

        human_readable = tableToMarkdown(
            f"Results for {hash_type} hash {hash}:",
            entry_context[context_key],
            headers=["SampleID", "FileName", "Type", "Verdict", "SampleURL"],
        )
        return_outputs(human_readable, entry_context, raw_response=raw_response)
    else:
        return_outputs(
            f"No samples found for {hash_type} hash {hash}",
            {},
        )


def get_job(job_id, sample_id):
    """
    Args:
        sample_id (str):
        job_id (str):
    Returns:
        dict of response, if not exists returns:
        {
            'error_msg': 'No such element'
            'result': 'error'
        }
    """
    suffix = f"job/{job_id}" if job_id else f"job/sample/{sample_id}"
    response = http_request("GET", suffix, ignore_errors=True)
    return response


def get_job_command():
    job_id = demisto.args().get("job_id")
    sample_id = demisto.args().get("sample_id")
    if sample_id:
        check_id(sample_id)
    else:
        check_id(job_id)

    vmray_id = job_id if job_id else sample_id
    title = "job" if job_id else "sample"

    raw_response = get_job(job_id=job_id, sample_id=sample_id)
    data = raw_response.get("data")
    if not data or raw_response.get("result") == "error":
        entry = build_finished_job(job_id=job_id, sample_id=sample_id)
        human_readable = f"#### Couldn't find a job for the {title}: {vmray_id}. Either the job completed, or does not exist."
    else:
        entry = build_job_data(data)
        sample = entry[0] if isinstance(entry, list) else entry
        human_readable = tableToMarkdown(
            f"Job results for {title} id: {vmray_id}",
            sample,
            headers=["JobID", "SampleID", "VMName", "VMID"],
        )

    entry_context = {"VMRay.Job(val.JobID === obj.JobID && val.SampleID === obj.SampleID)": entry}
    return_outputs(human_readable, entry_context, raw_response=raw_response)


def get_threat_indicators(sample_id):
    """

    Args:
        sample_id (str):

    Returns:
        dict: response
    """
    suffix = f"sample/{sample_id}/threat_indicators"
    response = http_request("GET", suffix).get("data")
    return response


def get_threat_indicators_command():
    sample_id = demisto.args().get("sample_id")
    check_id(sample_id)
    raw_response = get_threat_indicators(sample_id)
    data = raw_response.get("threat_indicators")

    # Build Entry Context
    if data and isinstance(data, list):
        entry_context_list = []
        for indicator in data:
            entry = {}
            entry["AnalysisID"] = indicator.get("analysis_ids")
            entry["Category"] = indicator.get("category")
            entry["Classification"] = indicator.get("classifications")
            entry["ID"] = indicator.get("id")
            entry["Operation"] = indicator.get("operation")
            entry_context_list.append(entry)

        human_readable = tableToMarkdown(
            f"Threat indicators for sample ID: {sample_id}:",
            entry_context_list,
            headers=["ID", "AnalysisID", "Category", "Classification", "Operation"],
        )

        entry_context = {"VMRay.ThreatIndicator(obj.ID === val.ID)": entry_context_list}
        return_outputs(human_readable, entry_context, raw_response={"threat_indicators": data})
    else:
        return_outputs(
            f"No threat indicators for sample ID: {sample_id}",
            {},
            raw_response=raw_response,
        )


def post_tags_to_analysis(analysis_id, tag):
    """

    Args:
        analysis_id (str):
        tag (str):

    Returns:
        dict:
    """
    suffix = f"analysis/{analysis_id}/tag/{tag}"
    response = http_request("POST", suffix)
    return response


def post_tags_to_submission(submission_id, tag):
    """

    Args:
        submission_id (str):
        tag (str):

    Returns:
        dict:

    """
    suffix = f"submission/{submission_id}/tag/{tag}"
    response = http_request("POST", suffix)
    return response


def post_tags():
    analysis_id = demisto.args().get("analysis_id")
    submission_id = demisto.args().get("submission_id")
    tag = demisto.args().get("tag")
    if not submission_id and not analysis_id:
        raise ValueError("No submission ID or analysis ID has been provided")
    if analysis_id:
        analysis_status = post_tags_to_analysis(analysis_id, tag)
        if analysis_status.get("result") == "ok":
            return_outputs(
                f"Tags: {tag} has been added to analysis: {analysis_id}",
                {},
                raw_response=analysis_status,
            )
    if submission_id:
        submission_status = post_tags_to_submission(submission_id, tag)
        if submission_status.get("result") == "ok":
            return_outputs(
                f"Tags: {tag} has been added to submission: {submission_id}",
                {},
                raw_response=submission_status,
            )


def delete_tags_from_analysis(analysis_id, tag):
    suffix = f"analysis/{analysis_id}/tag/{tag}"
    response = http_request("DELETE", suffix)
    return response


def delete_tags_from_submission(submission_id, tag):
    suffix = f"submission/{submission_id}/tag/{tag}"
    response = http_request("DELETE", suffix)
    return response


def delete_tags():
    analysis_id = demisto.args().get("analysis_id")
    submission_id = demisto.args().get("submission_id")
    tag = demisto.args().get("tag")
    if not submission_id and not analysis_id:
        raise ValueError("No submission ID or analysis ID has been provided")
    if submission_id:
        submission_status = delete_tags_from_submission(submission_id, tag)
        if submission_status.get("result") == "ok":
            return_outputs(
                f"Tags: {tag} has been removed from submission: {submission_id}",
                {},
                raw_response=submission_status,
            )
    if analysis_id:
        analysis_status = delete_tags_from_analysis(analysis_id, tag)
        if analysis_status.get("result") == "ok":
            return_outputs(
                f"Tags: {tag} has been removed from analysis: {analysis_id}",
                {},
                raw_response=analysis_status,
            )


def get_iocs(sample_id, all_artifacts):
    """

    Args:
        sample_id (str):

    Returns:
        dict: response
    """
    suffix = f"sample/{sample_id}/iocs"
    if all_artifacts:
        suffix += "?all_artifacts=true"
    response = http_request("GET", suffix)
    return response


def get_iocs_command():  # pragma: no cover
    def get_hashed(lst):
        """

        Args:
            lst (List[dict]): list of hashes attributes

        Returns:
            List[dict]:list of hashes attributes in demisto's favor
        """
        hashes_dict = {"MD5": "md5_hash", "SHA1": "sha1_hash", "SHA256": "sha256_hash", "SSDeep": "ssdeep_hash"}
        return [{k: hashes.get(v) for k, v in hashes_dict.items()} for hashes in lst]

    sample_id = demisto.args().get("sample_id")
    check_id(sample_id)
    all_artifacts = demisto.args().get("all_artifacts", "false").lower() == "true"
    raw_response = get_iocs(sample_id, all_artifacts)
    data = raw_response.get("data", {}).get("iocs", {})

    command_results_list = []

    indicator_types = {
        # mapping of
        # VMRay artifact type -> XSOAR score type,      Indicator class, Main value key, Headers
        "Domain": (DBotScoreType.DOMAIN, Common.Domain, "Domain", ["OriginalDomains", "Countries"]),  # noqa: E241, E501
        "EmailAddress": (DBotScoreType.EMAIL, Common.EMAIL, "EmailAddress", ["IsRecipient", "IsSender", "Subjects"]),
        # noqa: E241, E501
        "Email": (
            None,
            None,
            "Subject",
            [
                "Subject",
                "Sender",
                "Recipients",  # noqa: E241, E501
                "NrAttachments",
                "NrLinks",
            ],
        ),
        "Filename": (None, None, "Filename", ["Operations"]),  # noqa: E241
        "File": (
            DBotScoreType.FILE,
            None,
            "Filename",
            [
                "Filenames",
                "MD5",
                "SHA1",
                "SHA256",  # noqa: E241, E501
                "Operations",
            ],
        ),
        "IP": (DBotScoreType.IP, Common.IP, "IP", ["Domains", "Countries", "Protocols"]),  # noqa: E241, E501
        "Mutex": (None, None, "Name", ["Operations", "ParentProcessesNames"]),  # noqa: E241, E501
        "Process": (None, None, "ProcessNames", ["CmdLine"]),  # noqa: E241
        "Registry": (
            None,
            None,
            "Name",
            [
                "ValueTypes",
                "Operations",  # noqa: E241, E501
                "ParentProcessesNames",
            ],
        ),
        "URL": (
            DBotScoreType.URL,
            Common.URL,
            "URL",
            [
                "OriginalURLs",
                "Categories",  # noqa: E241, E501
                "Countries",
                "Methods",
                "IPAddresses",
                "ParentProcessesNames",
            ],
        ),
    }

    # this will be extended with every call to generate_results
    # we need to keep the state and always add new items to it, so that new results don't replace information from
    # older ones
    context_output = {"SampleID": sample_id, "IOC": {}}
    artifact_type = "artifact" if all_artifacts else "IOC"

    # helper function to generate the CommandResults objects from the IOC information
    def generate_results(vmray_type, objects):
        res = []
        dbot_score_type, indicator_class, key_field, headers = indicator_types[vmray_type]
        for object in objects:
            key_value = object[key_field]
            indicator = None
            if dbot_score_type == DBotScoreType.FILE:
                # special handing for File indicators since they need a hash as the indicator...
                hashes = object.get("Hashes", [{}])[0]
                dbot_score = Common.DBotScore(
                    indicator=hashes.get("MD5"),
                    indicator_type=dbot_score_type,
                    integration_name=VENDOR_NAME,
                    score=DBOTSCORE.get(object["Verdict"], 0),
                    reliability=RELIABILITY,
                )
                # ... and have multiple parameters
                indicator = Common.File(
                    dbot_score,
                    path=key_value,
                    size=object.get("FileSize"),
                    md5=hashes.get("MD5"),
                    sha1=hashes.get("SHA1"),
                    sha256=hashes.get("SHA256"),
                    ssdeep=hashes.get("SSDeep"),
                    file_type=object.get("MIMEType"),
                )
            elif dbot_score_type is not None and indicator_class:
                # Generic handling for IOCs which have a corresponding Indicator type in XSOAR
                dbot_score = Common.DBotScore(
                    indicator=key_value,
                    indicator_type=dbot_score_type,
                    integration_name=VENDOR_NAME,
                    score=DBOTSCORE.get(object["Verdict"], 0),
                    reliability=RELIABILITY,
                )
                # first argument must always be the "main" value and second arg the score
                indicator = indicator_class(key_value, dbot_score)

            # fields that should be shown in human-readable output
            table_headers = [key_field, "IsIOC"] + headers + ["Verdict", "VerdictReason"]

            # add IOC to the final context output
            if vmray_type in context_output["IOC"]:
                context_output["IOC"][vmray_type].append(object)
            else:
                context_output["IOC"][vmray_type] = [object]

            if dbot_score_type == DBotScoreType.FILE:
                # for files we put the hashes manually in the readable output
                info = object.copy()
                info.update(info.get("Hashes", [{}])[0])
            else:
                info = object

            try:
                # tableToMarkdown sometimes chokes on unicode input
                readable_output = tableToMarkdown(vmray_type + " " + artifact_type, info, headers=table_headers, removeNull=True)
            except UnicodeEncodeError:
                readable_output = " "

            res.append(
                CommandResults(
                    outputs_prefix="VMRay.Sample",
                    outputs_key_field="SampleID",
                    outputs=context_output,
                    readable_output=readable_output,
                    indicator=indicator,
                )
            )
        return res

    domains = data.get("domains", [])
    command_results_list.append(
        generate_results(
            "Domain",
            [
                {
                    "AnalysisID": domain.get("analysis_ids"),
                    "Countries": domain.get("countries"),
                    "CountryCodes": domain.get("country_codes"),
                    "Domain": domain.get("domain"),
                    "ID": 0,  # deprecated
                    "IsIOC": domain.get("ioc"),
                    "IOCType": domain.get("ioc_type"),
                    "IpAddresses": domain.get("ip_addresses"),
                    "OriginalDomains": domain.get("original_domains"),
                    "ParentProcesses": domain.get("parent_processes"),
                    "ParentProcessesNames": domain.get("parent_processes_names"),
                    "Protocols": domain.get("protocols"),
                    "Sources": domain.get("sources"),
                    "Type": domain.get("type"),
                    "Verdict": VERDICT_DICT.get(domain.get("verdict")),
                    "VerdictReason": domain.get("verdict_reason"),
                }
                for domain in domains
                if domain.get("domain")
            ],
        )
    )

    email_addresses = data.get("email_addresses", [])
    command_results_list.append(
        generate_results(
            "EmailAddress",
            [
                {
                    "AnalysisID": email_address.get("analysis_ids"),
                    "Classifications": email_address.get("classifications"),
                    "EmailAddress": email_address.get("email_address"),
                    "IsIOC": email_address.get("ioc"),
                    "IsRecipient": email_address.get("recipient"),
                    "IsSender": email_address.get("sender"),
                    "IOCType": email_address.get("ioc_type"),
                    "Subjects": email_address.get("subjects"),
                    "ThreatNames": email_address.get("threat_names"),
                    "Type": email_address.get("type"),
                    "Verdict": VERDICT_DICT.get(email_address.get("verdict")),
                    "VerdictReason": email_address.get("verdict_reason"),
                }
                for email_address in email_addresses
                if email_address.get("email_address")
            ],
        )
    )

    emails = data.get("emails", [])
    command_results_list.append(
        generate_results(
            "Email",
            [
                {
                    "AnalysisID": email.get("analysis_ids"),
                    "AttachmentTypes": email.get("attachment_types"),
                    "Classifications": email.get("classifications"),
                    "Hashes": get_hashed(email.get("hashes")),
                    "IsIOC": email.get("ioc"),
                    "IOCType": email.get("ioc_type"),
                    "NrAttachments": email.get("nr_attachments"),
                    "NrLinks": email.get("nr_links"),
                    "Recipients": email.get("recipients"),
                    "Sender": email.get("sender"),
                    "Subject": email.get("subject"),
                    "ThreatNames": email.get("threat_names"),
                    "Type": email.get("type"),
                    "Verdict": VERDICT_DICT.get(email.get("verdict")),
                    "VerdictReason": email.get("verdict_reason"),
                }
                for email in emails
            ],
        )
    )

    filenames = data.get("filenames", [])
    command_results_list.append(
        generate_results(
            "Filename",
            [
                {
                    "AnalysisID": filename.get("analysis_ids"),
                    "Categories": filename.get("categories"),
                    "Classifications": filename.get("classifications"),
                    "Filename": filename.get("filename"),
                    "IsIOC": filename.get("ioc"),
                    "IOCType": filename.get("ioc_type"),
                    "Operations": filename.get("operations"),
                    "ThreatNames": filename.get("threat_names"),
                    "Type": filename.get("type"),
                    "Verdict": VERDICT_DICT.get(filename.get("verdict")),
                    "VerdictReason": filename.get("verdict_reason"),
                }
                for filename in filenames
                if filename.get("filename")
            ],
        )
    )

    files = data.get("files", [])
    command_results_list.append(
        generate_results(
            "File",
            [
                {
                    "AnalysisID": file.get("analysis_ids"),
                    "Categories": file.get("categories"),
                    "Classifications": file.get("classifications"),
                    "FileSize": file.get("file_size"),
                    "Filename": file.get("filename"),
                    "Filenames": file.get("filenames"),
                    "Hashes": get_hashed(file.get("hashes")),
                    "ID": 0,  # deprecated
                    "IsIOC": file.get("ioc"),
                    "IOCType": file.get("ioc_type"),
                    "MIMEType": file.get("mime_type"),
                    "Name": file.get("filename"),  # for backwards compatibility
                    "NormFilename": file.get("norm_filename"),
                    "Operation": file.get("operations"),  # typo
                    "Operations": file.get("operations"),
                    "ParentFiles": file.get("parent_files"),
                    "ParentProcesses": file.get("parent_processes"),
                    "ParentProcessesNames": file.get("parent_processes_names"),
                    "ResourceURL": file.get("resource_url"),
                    "ThreatNames": file.get("threat_names"),
                    "Type": file.get("type"),
                    "Verdict": VERDICT_DICT.get(file.get("verdict")),
                    "VerdictReason": file.get("verdict_reason"),
                }
                for file in files
            ],
        )
    )

    ips = data.get("ips", [])
    command_results_list.append(
        generate_results(
            "IP",
            [
                {
                    "AnalysisID": ip.get("analysis_ids"),
                    "Country": ip.get("country"),
                    "CountryCode": ip.get("country_code"),
                    "Domains": ip.get("domains"),
                    "IP": ip.get("ip_address"),
                    "ID": 0,  # deprecated
                    "IsIOC": ip.get("ioc"),
                    "IOCType": ip.get("ioc_type"),
                    "Operation": None,  # deprecated
                    "ParentProcesses": ip.get("parent_processes"),
                    "ParentProcessesNames": ip.get("parent_processes_names"),
                    "Protocols": ip.get("protocols"),
                    "Sources": ip.get("sources"),
                    "Type": ip.get("type"),
                    "Verdict": VERDICT_DICT.get(ip.get("verdict")),
                    "VerdictReason": ip.get("verdict_reason"),
                }
                for ip in ips
                if ip.get("ip_address")
            ],
        )
    )

    mutexes = data.get("mutexes", [])
    command_results_list.append(
        generate_results(
            "Mutex",
            [
                {
                    "AnalysisID": mutex.get("analysis_ids"),
                    "Classifications": mutex.get("classifications"),
                    "ID": 0,  # deprecated
                    "IsIOC": mutex.get("ioc"),
                    "IOCType": mutex.get("ioc_type"),
                    "Name": mutex.get("mutex_name"),
                    "Operation": mutex.get("operations"),  # typo
                    "Operations": mutex.get("operations"),
                    "ParentProcesses": mutex.get("parent_processes"),
                    "ParentProcessesNames": mutex.get("parent_processes_names"),
                    "ThreatNames": mutex.get("threat_names"),
                    "Type": mutex.get("type"),
                    "Verdict": VERDICT_DICT.get(mutex.get("verdict")),
                    "VerdictReason": mutex.get("verdict_reason"),
                }
                for mutex in mutexes
                if mutex.get("mutex_name")
            ],
        )
    )

    processes = data.get("processes", [])
    command_results_list.append(
        generate_results(
            "Process",
            [
                {
                    "AnalysisID": process.get("analysis_ids"),
                    "Classifications": process.get("classifications"),
                    "CmdLine": process.get("cmd_line"),
                    "ImageNames": process.get("image_names"),
                    "IsIOC": process.get("ioc"),
                    "IOCType": process.get("ioc_type"),
                    "ParentProcesses": process.get("parent_processes"),
                    "ParentProcessesNames": process.get("parent_processes_names"),
                    "ProcessNames": process.get("process_names"),
                    "ThreatNames": process.get("threat_names"),
                    "Type": process.get("type"),
                    "Verdict": VERDICT_DICT.get(process.get("verdict")),
                    "VerdictReason": process.get("verdict_reason"),
                }
                for process in processes
                if process.get("process_names")
            ],
        )
    )

    registry = data.get("registry", [])
    command_results_list.append(
        generate_results(
            "Registry",
            [
                {
                    "AnalysisID": reg.get("analysis_ids"),
                    "Classifications": reg.get("classifications"),
                    "ID": 0,  # deprecated
                    "IsIOC": reg.get("ioc"),
                    "IOCType": reg.get("ioc_type"),
                    "Name": reg.get("reg_key_name"),
                    "Operation": reg.get("operations"),  # typo
                    "Operations": reg.get("operations"),
                    "ParentProcesses": reg.get("parent_processes"),
                    "ParentProcessesNames": reg.get("parent_processes_names"),
                    "ThreatNames": reg.get("threat_names"),
                    "Type": reg.get("type"),
                    "ValueTypes": reg.get("reg_key_value_types"),
                    "Verdict": VERDICT_DICT.get(reg.get("verdict")),
                    "VerdictReason": reg.get("verdict_reason"),
                }
                for reg in registry
                if reg.get("reg_key_name")
            ],
        )
    )

    urls = data.get("urls", [])
    command_results_list.append(
        generate_results(
            "URL",
            [
                {
                    "AnalysisID": url.get("analysis_ids"),
                    "Categories": url.get("categories"),
                    "ContentTypes": url.get("content_types"),
                    "Countries": url.get("countries"),
                    "CountryCodes": url.get("country_codes"),
                    "ID": 0,  # deprecated
                    "IPAddresses": url.get("ip_addresses"),
                    "Methods": url.get("methods"),
                    "Operation": None,  # deprecated
                    "OriginalURLs": url.get("original_urls"),
                    "ParentFiles": url.get("parent_files"),
                    "ParentProcesses": url.get("parent_processes"),
                    "ParentProcessesNames": url.get("parent_processes_names"),
                    "Referrers": url.get("referrers"),
                    "Source": url.get("sources"),
                    "Type": url.get("type"),
                    "URL": url.get("url"),
                    "UserAgents": url.get("user_agents"),
                    "Verdict": VERDICT_DICT.get(url.get("verdict")),
                    "VerdictReason": url.get("verdict_reason"),
                }
                for url in urls
                if url.get("url")
            ],
        )
    )

    return_results(command_results_list)


def get_summary(analysis_id):
    """

    Args:
        analysis_id (str):

    Returns:
        str: response
    """
    suffix = f"analysis/{analysis_id}/archive/logs/summary_v2.json"
    response = http_request("GET", suffix, get_raw=True)
    return response


def get_screenshots(analysis_id):
    """

    Args:
        analysis_id (str):

    Returns:
        str: response
    """
    suffix = f"analysis/{analysis_id}/archive?filenames=screenshots/*"
    response = http_request("GET", suffix, get_raw=True)
    return response


def get_summary_command():
    analysis_id = demisto.args().get("analysis_id")
    check_id(analysis_id)

    billing_type = get_billing_type(analysis_id)
    if billing_type == "detector":
        raise ValueError(
            "The current billing plan has no permissions to generate or download reports. "
            "If you want more information about the sample, use "
            "`vmray-get-threat-indicators` or `vmray-get-iocs` instead."
        )

    summary_data = get_summary(analysis_id)

    file_entry = fileResult(filename="summary_v2.json", data=summary_data, file_type=EntryType.ENTRY_INFO_FILE)
    return_results(file_entry)


def get_screenshots_command():
    analysis_id = demisto.args().get("analysis_id")
    check_id(analysis_id)

    screenshots_data = get_screenshots(analysis_id)

    file_results = []
    processed_screenshots = []
    screenshot_counter = 0
    try:
        with ZipFile(io.BytesIO(screenshots_data), "r") as screenshots_zip:
            index_log_data = screenshots_zip.read("screenshots/index.log")
            for line in index_log_data.splitlines():
                log_entry = ScreenshotLogEntry.parse(line)
                if log_entry.sha256 in processed_screenshots:
                    continue

                extension = os.path.splitext(log_entry.filename)[1]
                screenshot_data = screenshots_zip.read(f"screenshots/{log_entry.filename}")
                file_results.append(
                    fileResult(
                        filename=f"analysis_{analysis_id}_screenshot_{screenshot_counter}{extension}",
                        data=screenshot_data,
                        file_type=EntryType.IMAGE,
                    )
                )
                processed_screenshots.append(log_entry.sha256)
                screenshot_counter += 1
    except Exception as exc:  # noqa
        demisto.error(f"Failed to read screenshots.zip, error: {exc}")
        raise exc
    else:
        demisto.debug(f"Successfully read screenshots.zip, found {screenshot_counter} screenshots")

    return_results(file_results)


def vmray_get_license_usage_verdicts_command():  # pragma: no cover
    """

    Returns:
        dict: response
    """
    suffix = "billing_info"
    raw_response = http_request("GET", suffix)
    data = raw_response.get("data")

    entry = {}
    entry["VerdictsQuota"] = data.get("verdict_quota")
    entry["VerdictsRemaining"] = data.get("verdict_remaining")
    entry["VerdictsUsage"] = round(
        (100 / float(data.get("verdict_quota"))) * (float(data.get("verdict_quota")) - float(data.get("verdict_remaining"))), 2
    )
    entry["PeriodEndDate"] = data.get("end_date")

    markdown = tableToMarkdown(
        "VMRay Verdicts Quota Information",
        entry,
        headers=["VerdictsQuota", "VerdictsRemaining", "VerdictsUsage", "PeriodEndDate"],
    )

    results = CommandResults(
        readable_output=markdown, outputs_prefix="VMRay.VerdicsQuota", outputs_key_field="PeriodEndDate", outputs=entry
    )

    return_results(results)


def vmray_get_license_usage_reports_command():  # pragma: no cover
    """

    Returns:
        dict: response
    """
    suffix = "billing_info"
    raw_response = http_request("GET", suffix)
    data = raw_response.get("data")

    entry = {}
    entry["ReportQuota"] = data.get("report_quota")
    entry["ReportRemaining"] = data.get("report_remaining")
    entry["ReportUsage"] = round(
        (100 / float(data.get("report_quota"))) * (float(data.get("report_quota")) - float(data.get("report_remaining"))), 2
    )
    entry["PeriodEndDate"] = data.get("end_date")

    markdown = tableToMarkdown(
        "VMRay Reports Quota Information", entry, headers=["ReportQuota", "ReportRemaining", "ReportUsage", "PeriodEndDate"]
    )

    results = CommandResults(
        readable_output=markdown, outputs_prefix="VMRay.ReportsQuota", outputs_key_field="PeriodEndDate", outputs=entry
    )

    return_results(results)


def get_pdf_report(sample_id):
    """

    Args:
        sample_id (str):

    Returns:
        str: response
    """
    suffix = f"sample/{sample_id}/report"
    response = http_request("GET", suffix, get_raw=True)
    return response


def vmray_get_pdf_report_command():  # pragma: no cover
    """

    Returns:
        dict: response
    """
    sample_id = demisto.args().get("sample_id")
    check_id(sample_id)

    pdf_report = get_pdf_report(sample_id)
    file_entry = fileResult(filename=f"{sample_id}_report.pdf", data=pdf_report, file_type=EntryType.ENTRY_INFO_FILE)
    return_results(file_entry)


def main():  # pragma: no cover
    try:
        command = demisto.command()
        if command == "test-module":
            # This is the call made when pressing the integration test button.
            test_module()
        elif command in ("upload_sample", "vmray-upload-sample", "file"):
            upload_sample_command()
        elif command == "vmray-upload-url":
            upload_url_command()
        elif command == "vmray-get-submission":
            get_submission_command()
        elif command in ("get_results", "vmray-get-analysis-by-sample"):
            get_analysis_command()
        elif command == "vmray-get-sample":
            get_sample_command()
        elif command == "vmray-get-sample-by-hash":
            get_sample_by_hash_command()
        elif command in (
            "vmray-get-job-by-sample",
            "get_job_sample",
            "vmray-get-job-by-id",
        ):
            get_job_command()
        elif command == "vmray-get-threat-indicators":
            get_threat_indicators_command()
        elif command == "vmray-add-tag":
            post_tags()
        elif command == "vmray-delete-tag":
            delete_tags()
        elif command == "vmray-get-iocs":
            get_iocs_command()
        elif command == "vmray-get-summary":
            get_summary_command()
        elif command == "vmray-get-screenshots":
            get_screenshots_command()
        elif command == "vmray-get-license-usage-verdicts":
            vmray_get_license_usage_verdicts_command()
        elif command == "vmray-get-license-usage-reports":
            vmray_get_license_usage_reports_command()
        elif command == "vmray-get-pdf-report":
            vmray_get_pdf_report_command()
    except Exception as exc:
        return_error(f"Failed to execute `{demisto.command()}` command. Error: {exc!s}")


if __name__ in ("__builtin__", "builtins", "__main__"):
    main()