CrowdStrikeMalquery

Use the MalQuery Integration to query the contents of clean and malicious binary files, which forms part of Falcon's search engine.

Authentication & Identity Management · CrowdStrike Malquery

Details

IDCrowdStrikeMalquery
ProviderCrowdStrike
CategoryAuthentication & Identity Management
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658

README

Use the MalQuery Integration to query the contents of clean and malicious binary files, which forms part of Falcon’s search engine.
This integration was integrated and tested with version 1.0 of CrowdStrikeMalquery

Configure CrowdStrikeMalquery in Cortex

Parameter Description Required
base_url Server URL (e.g. https://example.net) True
client_id Client ID True
client_secret Client Secret True
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False

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.

cs-malquery-exact-search


Searches Falcon MalQuery for a combination of hex patterns and strings to identify malware samples based upon file content, which returns a request ID. Use the request ID in the cs-malquery-get-request command to retrieve results. You can filter results based on criteria such as file type, file size and first seen date.

Base Command

cs-malquery-exact-search

Input

Argument Name Description Required
hex The hex pattern to search. For example, deadbeef0102 (for bytes de, ad, be, ef, 01, 02). Optional
ascii The ASCII pattern to search. For example, CrowdStrike. Optional
wide The wide string pattern to search. For example, CrowdStrike. Optional
limit The maximum number of results to be returned. Default is 100. Optional
max_size The maximum file size. The value can be specified either in bytes or in multiples of KB/MB/GB. For example, 128000, 1.3 KB, 8mb. Optional
min_size The minimum file size. The value can be specified either in bytes or in multiples of KB/MB/GB. For example, 128000, 1.3 KB, 8mb. Optional
max_date Limits results to files first seen before this date. The format is YYYY/MM/DD. For example, 2018/01/31. Optional
min_date Limits results to files first seen after this date. The format is YYYY/MM/DD. For example, 2018/01/31. Optional
filter_filetypes Limits results to certain file types such as EMAIL, PCAP, PDF, PE32. Comma separated values. For a full list of file types, see the MalQuery API documentation. Optional
filter_meta Specifies a subset of metadata fields to return in the results. Possible values - sha256, md5, type, size, first_seen, label, family. Comma separated values. Optional

Context Output

Path Type Description
Malquery.Request_ID String The request ID.

Command Example

!cs-malquery-exact-search hex=8948208b480833ca33f989502489482889782c8bd7 filter_meta=sha256,type,size limit=5

Context Example

{
    "Malquery": {
        "Request_ID": "08942ddd-373e-493d-54f9-f6e495174913"
    }
}

Human Readable Output

Search Result

Request_ID
08942ddd-373e-493d-54f9-f6e495174913

cs-malquery-hunt


Schedules a YARA rule-based search for execution, which returns a request ID. Use the request ID in the cs-malquery-get-request command to retrieve results. You can filter based on criteria such as file type, file size and first seen date.

Base Command

cs-malquery-hunt

Input

Argument Name Description Required
yara_rule The YARA rule to search. Optional
yar_file_entry_id A YAR file entry ID that contains the YARA rule to search. Only one Yara rule per request. Optional
limit The maximum number of results to be returned. Optional
max_size The maximum file size. The value can be specified either in bytes or in multiples of KB/MB/GB. For example, 128000, 1.3 KB, 8mb. Optional
min_size The minimum file size. For example, 128000, 1.3 KB, 8mb. Optional
max_date Limits results to files first seen before this date. The format is YYYY/MM/DD. For example, 2018/01/31. Optional
min_date Limits results to files first seen after this date. The format is YYYY/MM/DD. For example, 2018/01/31. Optional
filter_filetypes Limits results to files of certain types such as EMAIL, PCAP, PDF, PE32. Comma separated values. For a full list of types, see the MalQuery API documentation. Optional
filter_meta Specifies a subset of metadata fields to return in the results. Possible values - sha256, md5, type, size, first_seen, label, family. Comma separated values. Optional

Context Output

Path Type Description
Malquery.Request_ID String The request ID.

Command Example

!cs-malquery-hunt file_type=PE32 filter_meta=sha256,type,size limit=5 yara_rule="rule CrowdStrike_16142_01 : wiper { strings: $ = { 41 61 43 63 64 44 65 46 66 47 68 69 4B 4C 6C 4D 6D 6E 4E 6F 4F 70 50 72 52 73 53 54 74 55 75 56 76 77 57 78 79 5A 7A 33 32 2E 5C 45 62 67 6A 48 49 20 5F 59 51 42 3A 22 2F 40 } condition: all of them and filesize < 800KB }"

Context Example

{
    "Malquery": {
        "Request_ID": "503efffd-2d44-4566-7794-8de45568cbbf"
    }
}

Human Readable Output

Search Result

Request_ID
503efffd-2d44-4566-7794-8de45568cbbf

cs-malquery-fuzzy-search


Searches Falcon MalQuery quickly. Uses partial matching, but with more potential for false positives. Search for a combination of hex patterns and strings to identify samples based upon file content.

Base Command

cs-malquery-fuzzy-search

Input

Argument Name Description Required
hex The hex pattern to search. For example, deadbeef0102 (for bytes de, ad, be, ef, 01, 02). Optional
ascii The ASCII pattern to search. For example, CrowdStrike. Optional
wide The wide string pattern to search. For example, CrowdStrike. Optional
limit The maximum number of results to be returned. Default is 100. Optional
filter_meta Specifies a subset of metadata fields to return in the results. Possible values - sha256, md5, type, size, first_seen, label, family. Comma separated values. Optional

Context Output

Path Type Description
Malquery.File.family String The malware family of the file. For example, StoneDrill.
Malquery.File.filesize String The size of the file.
Malquery.File.filetype String The type of the file.
Malquery.File.first_seen String The date when the file was first seen.
Malquery.File.label String The label of the file. For example, Malware.
Malquery.File.md5 String The MD5 of the file.
Malquery.File.sha1 String The SHA1 of the file.
Malquery.File.sha256 String The SHA256 of the file.

Command Example

!cs-malquery-fuzzy-search hex=8948208b480833ca33f989502489482889782c8bd7 filter_meta=sha256,type,size limit=5

Context Example

{
    "Malquery": {
        "File": [
            {
                "filesize": 310552,
                "filetype": "PE32",
                "sha256": "e51f0a8884eb08fc43da0501ebd3776831e2fd4b0a8dd12e69866a8febe41495"
            },
            {
                "filesize": 1672180,
                "filetype": "PE32",
                "sha256": "bc74f8fc37b902536b52c1157b74724edc96a586b0e3e38717dd845981443a5b"
            },
            {
                "filesize": 1672188,
                "filetype": "PE32",
                "sha256": "72b021085f62e5dc1335f878a2751bce68d95918c84215ec8dfebf491009ea09"
            },
            {
                "filesize": 279624,
                "filetype": "PE32",
                "sha256": "5e2e1735e10684b36d30b3a3362e66cd30fb493afac8e711d92bde8372b9b6d0"
            },
            {
                "filesize": 19055104,
                "filetype": "PE32",
                "sha256": "d5023cd464d7578506770338e0fc43bd64887dbf234785b4d8f8547e57efa33d"
            }
        ]
    }
}

Human Readable Output

Fuzzy Search Result

filesize filetype sha256
310552 PE32 e51f0a8884eb08fc43da0501ebd3776831e2fd4b0a8dd12e69866a8febe41495
1672180 PE32 bc74f8fc37b902536b52c1157b74724edc96a586b0e3e38717dd845981443a5b
1672188 PE32 72b021085f62e5dc1335f878a2751bce68d95918c84215ec8dfebf491009ea09
279624 PE32 5e2e1735e10684b36d30b3a3362e66cd30fb493afac8e711d92bde8372b9b6d0
19055104 PE32 d5023cd464d7578506770338e0fc43bd64887dbf234785b4d8f8547e57efa33d

cs-malquery-get-request


Checks the status and results of an asynchronous request, such as hunt or exact-search. Supports a single request ID.

Base Command

cs-malquery-get-request

Input

Argument Name Description Required
request_id The identifier of a MalQuery request. Required

Context Output

Path Type Description
Malquery.Request_ID String The request ID.
Malquery.Status String The status of the request.
Malquery.File.family String The malware family of the file. For example, StoneDrill.
Malquery.File.filesize String The size of the file.
Malquery.File.filetype String The type of the file. For example, PE32.
Malquery.File.first_seen String The date when the file was first seen.
Malquery.File.label String The label of the file. For example, malware.
Malquery.File.md5 String The file MD5.
Malquery.File.sha1 String The file SHA1.
Malquery.File.sha256 String The file SHA256.

Command Example

!cs-malquery-get-request request_id=096f6aa5-f245-4b09-790f-133bc89d4d26

Context Example

{
    "Malquery": {
        "File": [
            {
                "filesize": 151552,
                "filetype": "PE32",
                "first_seen": "2020/06/09",
                "label": "unknown",
                "md5": "0b189ab69d40e782fe827c63e1cc6f06",
                "samples": [],
                "sha1": "85be23059c9abb3370586dc49dbd8f1ced05df8e",
                "sha256": "d207ccf1eabcc9453288896d963f1a1c558c427abfe9305d7328e3a6fb06f6ee"
            },
            {
                "family": "Stonedrill",
                "filesize": 245760,
                "filetype": "PE32",
                "first_seen": "2019/03/12",
                "label": "malware",
                "md5": "688bdedf1f9dd44d6db51a7f8499939c",
                "samples": [],
                "sha1": "2ca2622317bc840bf890d1e337d2c547be2cfebf",
                "sha256": "41a1d7b98d0ce3259270c9a8f26fe8899cca402cba69ef8e5c70449faea8b714"
            },
            {
                "family": "Stonedrill",
                "filesize": 317440,
                "filetype": "PE32",
                "first_seen": "2018/01/24",
                "label": "malware",
                "md5": "345ade2a73ee83e4f75447a26c4e78c9",
                "samples": [],
                "sha1": "6ae00484a878201e6150108ca1b234dd1f68930d",
                "sha256": "0f191518ab7f24643218bd3384ae4bd1f52ec80419730d87196605a2a69938d7"
            },
            {
                "family": "Cadlotcorg",
                "filesize": 128512,
                "filetype": "PE32",
                "first_seen": "2017/07/20",
                "label": "malware",
                "md5": "2b82ce15a632e3ce1485bfc87e586ee5",
                "samples": [],
                "sha1": "df07d50296914de0ca3116d4ca6d3845d55c7540",
                "sha256": "3fb85b787fa005e591cd2cd7e1e83c79d103b1c26f5da31fdf788764ae0b8bb0"
            },
            {
                "family": "Cadlotcorg",
                "filesize": 130560,
                "filetype": "PE32",
                "first_seen": "2016/12/09",
                "label": "malware",
                "md5": "697c515a46484be4f9597cb4f39b2959",
                "samples": [],
                "sha1": "b9fc1ac4a7ccee467402f190391974a181391da3",
                "sha256": "bf79622491dc5d572b4cfb7feced055120138df94ffd2b48ca629bb0a77514cc"
            }
        ],
        "Request_ID": "096f6aa5-f245-4b09-790f-133bc89d4d26",
        "Status": "done"
    }
}

Human Readable Output

Search Result for request: 096f6aa5-f245-4b09-790f-133bc89d4d26

filesize filetype first_seen label md5 sha1 sha256
151552 PE32 2020/06/09 unknown 0b189ab69d40e782fe827c63e1cc6f06 85be23059c9abb3370586dc49dbd8f1ced05df8e d207ccf1eabcc9453288896d963f1a1c558c427abfe9305d7328e3a6fb06f6ee
245760 PE32 2019/03/12 malware 688bdedf1f9dd44d6db51a7f8499939c 2ca2622317bc840bf890d1e337d2c547be2cfebf 41a1d7b98d0ce3259270c9a8f26fe8899cca402cba69ef8e5c70449faea8b714
317440 PE32 2018/01/24 malware 345ade2a73ee83e4f75447a26c4e78c9 6ae00484a878201e6150108ca1b234dd1f68930d 0f191518ab7f24643218bd3384ae4bd1f52ec80419730d87196605a2a69938d7
128512 PE32 2017/07/20 malware 2b82ce15a632e3ce1485bfc87e586ee5 df07d50296914de0ca3116d4ca6d3845d55c7540 3fb85b787fa005e591cd2cd7e1e83c79d103b1c26f5da31fdf788764ae0b8bb0
130560 PE32 2016/12/09 malware 697c515a46484be4f9597cb4f39b2959 b9fc1ac4a7ccee467402f190391974a181391da3 bf79622491dc5d572b4cfb7feced055120138df94ffd2b48ca629bb0a77514cc

cs-malquery-get-ratelimit


Returns information about search and download quotas in your environment.

Base Command

cs-malquery-get-ratelimit

Context Output

Path Type Description
Malquery.Quota.hunt_count number How many hunts were executed in the last month.
Malquery.Quota.download_count number How many downloads were executed in the last month.
Malquery.Quota.monitor_count number How many monitors were created in the last month.
Malquery.Quota.hunt_limit number Total hunt limit per month.
Malquery.Quota.download_limit number The total download limit per month.
Malquery.Quota.monitor_limit number The total monitor limit per month.
Malquery.Quota.refresh_time string The time when the limits are refreshed. ISO 8601 format.
Malquery.Quota.days_left number The days left until the limits are refreshed.
Malquery.Quota.hunt_counts.userid String The download counts per user.
Malquery.Quota.hunt_counts.counter number The download counts per user.

Command Example


#### Context Example

{
“Malquery”: {
“Quota”: {
“days_left”: 3,
“download_count”: 28,
“download_counts”: [
{
“counter”: 28,
“userid”: “”
}
],
“download_limit”: 50,
“hunt_count”: 83,
“hunt_counts”: [
{
“counter”: 83,
“userid”: “”
}
],
“hunt_limit”: 100,
“monitor_count”: 0,
“monitor_limit”: 10,
“refresh_time”: “2020-08-01T00:00:00Z”
}
}
}


#### Human Readable Output

>### Quota Data
>
>|hunt_count|download_count|monitor_count|hunt_limit|download_limit|monitor_limit|refresh_time|days_left|
>|---|---|---|---|---|---|---|---|
>| 83 | 28 | 0 | 100 | 50 | 10 | 2020-08-01T00:00:00Z | 3 |

### cs-malquery-samples-multidownload

***
Schedule samples for download, which returns a request ID. Use the request ID in the cs-malquery-get-request, to check the status of the operation. When the request status is “done”, use the cs-malquery-sample-fetch to download the results as a password-protected archive. The password to extract results from the archive: infected'

#### Base Command

`cs-malquery-samples-multidownload`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| samples | Sample sha256 IDs. Comma separated values. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Malquery.Request_ID | String | The request ID. |
| Malquery.Status | String | The request status. |

#### Command Example

```!cs-malquery-samples-multidownload samples=742db9f3ae1b7322dfe8ab81476cd3146f9c0ce086fc4cd38a1072fb6cae8662,accc6794951290467e01b7676e8b4ba177076d54f836589ea7d3298cdf6fc995```

#### Context Example

{
“Malquery”: {
“Request_ID”: “e2e1aecb-6e34-44f7-5d42-932880276c5e”
}
}


#### Human Readable Output

>### Samples Multidownload Request
>
>|Request_ID|
>|---|
>| e2e1aecb-6e34-44f7-5d42-932880276c5e |

### cs-malquery-file-download

***
Download a file indexed by MalQuery. Specify the file using its SHA256. Only one file is supported.

#### Base Command

`cs-malquery-file-download`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| file_id | The SHA256 file. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Name | String | The name of the file. |
| File.EntryID | String | The Entry ID. |
| File.Info | String | Information about the file. |
| File.Type | String | The type of the file. |
| File.MD5 | String | MD5 hash of the file. |

#### Command Example

```!cs-malquery-file-download file_id=d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766```

#### Context Example

{
“File”: {
“EntryID”: “3993@904ba2df-a395-4270-8a6b-e9b8d614911e”,
“Info”: “application/x-dosexec”,
“MD5”: “c6a6a731f341ced1d93b61bc7628721d”,
“Name”: “d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766”,
“SHA1”: “8953cdddbe825e8378c590084dca1e3d76ced233”,
“SHA256”: “d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766”,
“SHA512”: “09c164142da9b4d8decb3cbbfa2916326aeab50d79bffa6090816f2cdb89838ac79fc7451c5997c06a9186c1bc0661283fc86cd5838d40e4b07b8a5d6bd8bb13”,
“SSDeep”: “24576:qripAq8fCAmHY/Ph9Kv18re7NHHReC3Mm:quptkhOveSBImMm”,
“Size”: 1076152,
“Type”: “PE32 executable (DLL) (GUI) Intel 80386, for MS Windows”
}
}


### cs-malquery-sample-fetch

***
Fetches a zip archive file using the password, "infected" containing the samples. Use this after the cs-malquery-samples-multidownload request has finished processing.

#### Base Command

`cs-malquery-sample-fetch`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| request_id | Identifier of a MalQuery request. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| File.SHA256 | String | THe SHA256 hash of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Name | String | The name of the file. |
| File.EntryID | String | The entry ID. |
| File.Info | String | Information about the file. |
| File.Type | String | The type of the file. |
| File.MD5 | String | The MD5 hash of the file. |

#### Command Example

```!cs-malquery-sample-fetch request_id=93b55373-3b69-43cb-6ea1-2870a44e1c1e```

#### Context Example

{
“File”: {
“EntryID”: “3517@904ba2df-a395-4270-8a6b-e9b8d614911e”,
“Info”: “application/zip”,
“MD5”: “a19a427b08c84f27a5a2b9f59fd8f752”,
“Name”: “93b55373-3b69-43cb-6ea1-2870a44e1c1e”,
“SHA1”: “01583032c5b9df88266da1d0cc46d41678203f2d”,
“SHA256”: “a959753414fcf8367054e195424d86feb75f3819272754178efc6ec0963f6bb0”,
“SHA512”: “3cce7bbca53570da2925da32942c7f20f1f619b016395a663c0abdb1e54eed06ad0b7b60687286f04278196693b4e44255c2cf25ed953a1c512d58625775704b”,
“SSDeep”: “49152:Pm7wDIVHR7jadrFtkGU5fW/Lv4yZ4mXoGODTmtvL7ERwjU3IDcJA2I:O7wDAHgfkpfiv4PmXoGO3mhLIRwL7”,
“Size”: 3059253,
“Type”: “Zip archive data, at least v1.0 to extract”
}
}


#### Human Readable Output

### file

***
Retrieves indexed files metadata by their hash.

#### Base Command

`file`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| file | The SHA256 of the files. Comma separated values. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Indicator | String | The indicator that was tested. |
| File.Vendor | String | The vendor used to calculate the score. |
| Malquery.File.family | String | The malware family of the file. For example, StoneDrill. |
| Malquery.File.filesize | String | The size of the file. |
| Malquery.File.filetype | String | The type of the file. |
| Malquery.File.first_seen | String | The date when the file was first seen. |
| Malquery.File.label | String | The label of the file. |
| Malquery.File.md5 | String | The MD5 hash of the file. |
| Malquery.File.sha1 | String | The SHA1 hash of the file. |
| Malquery.File.sha256 | String | The SHA256 hash of the file. |

#### Command Example

```!file file=d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766```

#### Context Example

{
“DBotScore”: {
“Indicator”: “d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766”,
“Score”: 0,
“Type”: “file”,
“Vendor”: “CrowdStrike Malquery”
},
“File”: {
“SHA256”: “d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766”
},
“Malquery”: {
“File”: {
“filesize”: 1076152,
“filetype”: “PE32”,
“first_seen”: “2014/01/27”,
“label”: “unknown”,
“md5”: “c6a6a731f341ced1d93b61bc7628721d”,
“sha1”: “8953cdddbe825e8378c590084dca1e3d76ced233”,
“sha256”: “d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766”
}
}
}
```

Human Readable Output

CrowdStrike Malquery File reputation

filesize filetype first_seen label md5 sha1 sha256
1076152 PE32 2014/01/27 unknown c6a6a731f341ced1d93b61bc7628721d 8953cdddbe825e8378c590084dca1e3d76ced233 d77cf874521ee7d4bb7f54bd8cef3d60ec24d267cf2d502f819880f0819f5766

Configuration parameters

  • base_url — Server URL (e.g. https://example.net) (required)
  • client_id — Client ID
  • client_secret — Client Secret
  • credentials_client — Client ID
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • integrationReliability — Source Reliability
  • feedExpirationPolicy
  • feedExpirationInterval

Commands (9)

  • cs-malquery-exact-search

    Searches Falcon MalQuery for a combination of hex patterns and strings to identify malware samples based upon file content, which returns a request ID. Use the request ID in the cs-malquery-get-request command to retrieve results. You can filter results based on criteria such as file type, file size and first seen date.

  • cs-malquery-file-download

    Download a file indexed by MalQuery. Specify the file using its SHA256. Only one file is supported.

  • cs-malquery-fuzzy-search

    Searches Falcon MalQuery quickly. Uses partial matching, but with more potential for false positives. Search for a combination of hex patterns and strings to identify samples based upon file content.

  • cs-malquery-get-ratelimit

    Returns information about search and download quotas in your environment.

  • cs-malquery-get-request

    Checks the status and results of an asynchronous request, such as hunt or exact-search. Supports a single request ID.

  • cs-malquery-hunt

    Schedules a YARA rule-based search for execution, which returns a request ID. Use the request ID in the cs-malquery-get-request command to retrieve results. You can filter based on criteria such as file type, file size and first seen date.

  • cs-malquery-sample-fetch

    Fetches a zip archive file using the password, "infected" containing the samples. Use this after the cs-malquery-samples-multidownload request has finished processing.

  • cs-malquery-samples-multidownload

    Schedule samples for download, which returns a request ID. Use the request ID in the cs-malquery-get-request, to check the status of the operation. When the request status is “done”, use the cs-malquery-sample-fetch to download the results as a password-protected archive. The password to extract results from the archive: infected'

  • file

    Retrieves indexed files metadata by their hash.

import demistomock as demisto
import urllib3
from CommonServerPython import *  # noqa: E402 lgtm [py/polluting-import]

from CommonServerUserPython import *  # noqa: E402 lgtm [py/polluting-import]

# Disable insecure warnings
urllib3.disable_warnings()

# CONSTANTS
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
VENDOR_NAME = "CrowdStrikeMalquery"
DBOT_SCORE = {
    "unknown": 0,
    "clean": 1,
    "unwanted": 2,
    "malware": 3,
    "malicious": 3,
}

# Note: True life time of token is actually 30 mins
TOKEN_LIFE_TIME = 28


def get_passed_mins(start_time, end_time_str, tz=None):
    """
    Returns the time passed in mins
    :param start_time: Start time in datetime
    :param end_time_str: End time in str
    :return: The passed mins in int
    """
    time_delta = start_time - datetime.fromtimestamp(end_time_str, tz)
    return time_delta.seconds / 60


class Client(BaseClient):
    """
    Client will implement the service API, and should not contain any Demisto logic.
    Should only do requests and return data.
    """

    def __init__(self, base_url, verify, proxy, client_id, client_secret):
        super().__init__(base_url=base_url, verify=verify, proxy=proxy)
        self.client_id = (client_id,)
        self.client_secret = client_secret

    def http_request(self, *args, headers=None, **kwargs):
        """
        Overrides Base client request function, retrieves and adds to headers access token before sending the request.

        Returns:
            requests.Response: The http response
        """
        token = self.get_access_token()
        default_headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        }
        if headers:
            default_headers.update(headers)

        return super()._http_request(*args, headers=default_headers, **kwargs)  # type: ignore[misc]

    def get_access_token(self):
        """
        Obtains access and refresh token from server.
        Access token is used and stored in the integration context until expiration time.
        After expiration, new refresh token and access token are obtained and stored in the
        integration context.

        Returns:
            str: Access token that will be added to authorization header.
        """
        now = datetime.now()
        integration_context = (
            demisto.getIntegrationContext()[0]
            if isinstance(demisto.getIntegrationContext(), list)
            else demisto.getIntegrationContext()
        )
        access_token = integration_context.get("access_token")
        valid_until = integration_context.get("valid_until")
        if access_token:
            if get_passed_mins(now, valid_until) >= TOKEN_LIFE_TIME:
                # token expired
                access_token = self.get_token_request()
                integration_context = {"access_token": access_token, "valid_until": date_to_timestamp(now) / 1000}
                demisto.setIntegrationContext(integration_context)
            return access_token
        else:
            # there's no token
            access_token = self.get_token_request()
            integration_context = ({"access_token": access_token, "valid_until": date_to_timestamp(now) / 1000},)
            demisto.setIntegrationContext(integration_context)
            return access_token

    def get_token_request(self):
        """
        Sends token request

        :rtype ``str``
        :return: Access token
        """
        body = {"client_id": self.client_id, "client_secret": self.client_secret}
        headers = {
            "accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
        }
        token_response = self._http_request(
            method="POST", full_url="https://api.crowdstrike.com/oauth2/token", url_suffix="", data=body, headers=headers
        )
        if not token_response:
            err_msg = (
                "Authorization Error: User has no authorization to create a token."
                " Please make sure you entered the credentials correctly."
            )
            raise Exception(err_msg)
        return token_response.get("access_token")

    def exact_search(self, body):
        return self.http_request(method="POST", url_suffix="/queries/exact-search/v1", json_data=body)

    def fuzzy_search(self, body):
        return self.http_request(method="POST", url_suffix="/combined/fuzzy-search/v1", json_data=body, timeout=40)

    def hunt(self, body):
        return self.http_request(method="POST", url_suffix="/queries/hunt/v1", json_data=body)

    def get_request(self, request_id):
        params = {"ids": request_id}
        return self.http_request(method="GET", url_suffix="/entities/requests/v1", params=params)

    def get_quotas(self):
        return self.http_request(method="GET", url_suffix="/aggregates/quotas/v1")

    def file_download(self, file_id):
        headers = {"accept": "application/octet-stream"}
        params = {"ids": file_id}
        return self.http_request(
            method="GET", url_suffix="/entities/download-files/v1", headers=headers, params=params, resp_type="response"
        )

    def samples_multidownload(self, body):
        return self.http_request(method="POST", url_suffix="/entities/samples-multidownload/v1", json_data=body)

    def fetch_samples(self, request_id):
        headers = {"accept": "application/zip"}
        params = {"ids": request_id}
        return self.http_request(
            method="GET", url_suffix="/entities/samples-fetch/v1", headers=headers, params=params, resp_type="response"
        )

    def get_files_metadata(self, files_ids):
        params = {"ids": files_ids}
        return self.http_request(method="GET", url_suffix="/entities/metadata/v1", params=params)


def test_module(client: Client, args: dict):
    """
    Returning 'ok' indicates that an access token was obtained successfully. Connection to the service is successful.

    Args:
        client:  Client

    Returns:
        'ok' if test passed, anything else will fail the test.
    """
    try:
        client.get_quotas()
    except Exception as e:
        raise DemistoException(f"Test failed. Please check your parameters. \n {e}")
    return "ok"


def exact_search_command(client: Client, args: dict) -> CommandResults:
    pattern_names = ["hex", "ascii", "wide"]
    patterns = [{"type": key, "value": args[key]} for key in pattern_names if args.get(key)]

    # must provide a pattern (hex, ascii ot wide string)
    if not patterns:
        raise DemistoException("You must provide a query to search in one of the following patterns: Hex, ASCII, Wide string")

    # dates format: YYYY/MM/DD
    query_filters = assign_params(
        limit=int(args.get("limit", "100")),
        filter_meta=argToList(args.get("filter_meta")),
        filter_filetypes=argToList(args.get("filter_filetypes")),
        max_size=args.get("max_size"),
        min_size=args.get("min_size"),
        max_date=args.get("max_date"),
        min_date=args.get("min_date"),
    )
    body = {"options": query_filters, "patterns": patterns}
    raw_response = client.exact_search(body)
    entry_context = {"Request_ID": raw_response.get("meta", {}).get("reqid")}

    human_readable = tableToMarkdown("Search Result", entry_context, removeNull=True)

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery",
        outputs_key_field="Request_ID",
        outputs=entry_context,
        raw_response=raw_response,
    )


def fuzzy_search_command(client: Client, args: dict) -> CommandResults:
    pattern_names = ["hex", "ascii", "wide"]
    patterns = [{"type": key, "value": args[key]} for key in pattern_names if args.get(key)]
    # must provide a pattern (hex, ascii ot wide string)
    if not patterns:
        raise DemistoException("You must provide a query to search in the following patterns: Hex, ASCII, Wide string")
    query_filters = assign_params(limit=int(args.get("limit", "100")), filter_meta=argToList(args.get("filter_meta")))
    body = {"options": query_filters, "patterns": patterns}
    raw_response = client.fuzzy_search(body)
    resources_found = raw_response.get("resources", {})
    human_readable = tableToMarkdown("Fuzzy Search Result", resources_found, removeNull=True)

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery.File(val.md5 && val.md5 == obj.md5 || val.sha256 && val.sha256 == obj.sha256)",
        outputs_key_field="",
        outputs=resources_found,
        raw_response=raw_response,
    )


def hunt_command(client: Client, args: dict) -> CommandResults:
    yara_rule = args.get("yara_rule")
    yar_file_entry_id = args.get("yar_file_entry_id")
    if not (yara_rule or yar_file_entry_id):
        raise DemistoException("You must provide either a YARA rule or a YAR file in order to execute the HUNT command")

    if yar_file_entry_id:
        file_path = demisto.getFilePath(yar_file_entry_id).get("path")
        with open(file_path, "rb") as file:
            yara_rule = file.read().decode("utf-8")

    # dates format: YYYY/MM/DD
    query_filters = assign_params(
        limit=int(args.get("limit", "100")),
        filter_meta=argToList(args.get("filter_meta")),
        filter_filetypes=argToList(args.get("filter_filetypes")),
        max_size=args.get("max_size"),
        min_size=args.get("min_size"),
        max_date=args.get("max_date"),
        min_date=args.get("min_date"),
    )
    body = {"options": query_filters, "yara_rule": yara_rule}
    raw_response = client.hunt(body)
    entry_context = {"Request_ID": raw_response.get("meta", {}).get("reqid")}
    human_readable = tableToMarkdown("Search Result", entry_context, removeNull=True)

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery",
        outputs_key_field="Request_ID",
        outputs=entry_context,
        raw_response=raw_response,
    )


def get_request_command(client: Client, args: dict) -> CommandResults:
    request_id = args.get("request_id")
    raw_response = client.get_request(request_id)
    resources = raw_response.get("resources")
    status = raw_response.get("meta", {}).get("status")

    # Possible values: inprogress, failed, done
    if status != "done":
        entry_context = {"Request_ID": request_id, "Status": status}
        human_readable = tableToMarkdown("Request Status:", entry_context, removeNull=True)
    else:
        entry_context = {"Request_ID": request_id, "Status": status, "File": resources if resources else None}
        human_readable = tableToMarkdown(f"Search Result for request: {request_id}", resources, removeNull=True)

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery",
        outputs_key_field="Request_ID",
        outputs=entry_context,
        raw_response=raw_response,
    )


def get_file_metadata_command(client: Client, args: dict):
    files_ids = argToList(args.get("file"))
    raw_response = client.get_files_metadata(files_ids)
    files = raw_response.get("resources", [])
    command_results: List[CommandResults] = []

    for file in files:
        file_label = file.get("label")
        sha256 = file.get("sha256")
        dbot_score = Common.DBotScore(
            indicator=sha256,
            indicator_type=DBotScoreType.FILE,
            integration_name=VENDOR_NAME,
            score=DBOT_SCORE[file_label],
            reliability=demisto.params().get("integrationReliability"),
        )
        file_entry = Common.File(sha256=sha256, md5=file.get("md5"), sha1=file.get("sha1"), dbot_score=dbot_score)
        table_name = f"{VENDOR_NAME} File reputation for: {sha256}"
        md = tableToMarkdown(table_name, file, removeNull=True)

        command_results.append(
            CommandResults(
                outputs_prefix="Malquery.File",
                outputs_key_field="sha256",
                outputs=file,
                readable_output=md,
                raw_response=raw_response,
                indicator=file_entry,
            )
        )

    return command_results


def file_download_command(client: Client, args: dict):
    file_id = args.get("file_id")
    raw_response = client.file_download(file_id)
    try:
        content = raw_response.content
    except Exception as e:
        raise DemistoException(f"Failed to load file data. \n {e}")

    return fileResult(file_id, content)


# Malquery counts the download as the number of sha256 passed to the endpoint and not as a single download.
def samples_multidownload_command(client: Client, args: dict) -> CommandResults:
    samples = argToList(args.get("samples"))
    body = {"samples": samples}
    raw_response = client.samples_multidownload(body)
    entry_context = {"Request_ID": raw_response.get("meta", {}).get("reqid")}
    human_readable = tableToMarkdown("Samples Multidownload Request", entry_context, removeNull=True)

    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery",
        outputs_key_field="Request_ID",
        outputs=entry_context,
        raw_response=raw_response,
    )


def samples_fetch_command(client: Client, args: dict):
    request_id = args.get("request_id")
    raw_response = client.fetch_samples(request_id)
    try:
        content = raw_response.content
        return fileResult(request_id, content)
    except DemistoException as e:
        if str(e).find("Could not find sample archive"):
            return "Could not find sample archive, The file is not indexed by MalQuery."
        else:
            raise


def get_ratelimit_command(client: Client, args: dict) -> CommandResults:
    raw_response = client.get_quotas()
    meta = raw_response.get("meta", {})
    headers = [
        "hunt_count",
        "download_count",
        "monitor_count",
        "hunt_limit",
        "download_limit",
        "monitor_limit",
        "refresh_time",
        "days_left",
    ]
    human_readable = tableToMarkdown("Quota Data", meta, headers=headers, removeNull=True)
    return CommandResults(
        readable_output=human_readable,
        outputs_prefix="Malquery.Quota",
        outputs_key_field="refresh_time",
        outputs=meta,
        raw_response=raw_response,
    )


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    params = demisto.params()
    client_id: str = params.get("credentials_client").get("identifier") or params.get("client_id")
    client_secret: str = params.get("credentials_client").get("password") or params.get("client_secret")
    base_url: str = urljoin(params.get("base_url", "").rstrip("/"), "/malquery")
    verify_certificate: bool = not params.get("insecure", False)
    proxy: bool = params.get("proxy", False)
    if not (client_id and client_secret):
        raise DemistoException("Client ID and Client Secret must be provided.")
    commands = {
        "test-module": test_module,
        "cs-malquery-exact-search": exact_search_command,
        "cs-malquery-fuzzy-search": fuzzy_search_command,
        "cs-malquery-hunt": hunt_command,
        "cs-malquery-get-request": get_request_command,
        "file": get_file_metadata_command,
        "cs-malquery-file-download": file_download_command,
        "cs-malquery-samples-multidownload": samples_multidownload_command,
        "cs-malquery-sample-fetch": samples_fetch_command,
        "cs-malquery-get-ratelimit": get_ratelimit_command,
    }
    command = demisto.command()
    LOG(f"Command being called is {command}")

    try:
        handle_proxy()
        client = Client(
            base_url=base_url, verify=verify_certificate, client_id=client_id, client_secret=client_secret, proxy=proxy
        )

        if command in commands:
            return_results(commands[command](client, demisto.args()))

    # Log exceptions
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}")


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