Skip to content

Commit 2b958b8

Browse files
Merge pull request #36 from MislavReversingLabs/main
v2.4.2
2 parents 21de1cb + 32d1139 commit 2b958b8

File tree

5 files changed

+107
-67
lines changed

5 files changed

+107
-67
lines changed

CHANGELOG.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ v2.3.0 (2023-09-29)
157157
---
158158

159159

160-
v2.4.0 (2023-12-29)
160+
v2.4.0 (2023-12-29) - [YANKED]
161161
-------------------
162162

163+
**Note:** Contains breaking changes in the `ExpressionSearch` class. We recommend using **v2.4.2**
164+
163165
#### Improvements
164166

165167
- **ticloud** module:
@@ -173,15 +175,35 @@ v2.4.0 (2023-12-29)
173175
---
174176

175177

176-
v2.4.1 (2024-01-11)
178+
v2.4.1 (2024-01-11) - [YANKED]
177179
-------------------
178180

181+
**Note:** Contains breaking changes in the `ExpressionSearch` class. We recommend using **v2.4.2**
182+
179183
#### Improvements
180184

181185
- **ticloud** module:
182186
- The `get_dynamic_analysis_results` method of the `DynamicAnalysis` class now also supports using a URL-s SHA-1 hash for fetching the URL dynamic analysis results.
183187

184188
- Error handling: Custom error classes now also carry the original response object. Users can now reach the original status code, error message and all other response properties using the caught error's `response_object` property.
189+
---
190+
191+
192+
v2.4.2 (2024-01-22)
193+
-------------------
194+
195+
All changes are calculated agains **v2.3.0**
196+
197+
### Improvements
198+
- **ticloud** module:
199+
- Added the `NewFilesFirstScan`, `NewFilesFirstAndRescan`, `FilesWithDetectionChanges`, `CvesExploitedInTheWild`, `NewExploitOrCveSamplesFoundInWildHourly`, `NewExploitAndCveSamplesFoundInWildDaily`, `NewWhitelistedFiles`, `ChangesWhitelistedFiles`, `MalwareFamilyDetection`, `ExpressionSearch`, `VerticalFeedStatistics` and `VerticalFeedSearch` classes.
200+
- The following changes were made to the `DynamicAnalysis` class:
201+
- Added `windows11` and `linux` to available Dynamic Analysis platforms.
202+
- Added the `detonate_url` method.
203+
- The `get_dynamic_analysis_results` method now supports fetching the URL dynamic analysis results using the URL string or its SHA-1 hash as a parameter.
204+
205+
- Added TitaniumCloud API codes to the README for better correspondence and orientation.
206+
- Error handling: Custom error classes now also carry the original response object. Users can now reach the original status code, error message and all other response properties using the caught error's `response_object` property.
185207

186208

187209

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2023 ReversingLabs
1+
Copyright (c) 2024 ReversingLabs
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -311,20 +311,23 @@ _TCA-0320_
311311
- Accepts a search query string and performs advanced search on the API
312312
- Returns a list of results aggregated through multiple paginated requests
313313

314-
315314
#### Class:
316315
```python
317316
class ExpressionSearch(TiCloudAPI)
318-
````
317+
```
319318
_TCA-0306_
320319
#### Methods:
321320
- `search`
322-
- Accepts a list containing the search query and performs expression search on the API
323-
- Returns only one defined page of results using one request
321+
- Provides samples first seen on a particular date, filtered by search criteria.
324322
- `search_aggregated`
325-
- Accepts a list containing the search query and performs expression search on the API
326-
- Returns a list of results aggregated through multiple paginated requests
327-
323+
- Provides samples first seen on a particular date, filtered by search criteria.
324+
- This method performs the paging automatically.
325+
- `get_latest_expression`
326+
- Provdes samples for yesterday’s date tha match the requested criteria.
327+
- `statistics_search`
328+
- Returns statistics about new samples in ReversingLabs TitaniumCloud on the requested date that match the used search criteria.
329+
- `get_latest_statistics`
330+
- Returns statistics about new samples in ReversingLabs TitaniumCloud from yesterday's date.
328331

329332
#### Class:
330333
```python
@@ -786,21 +789,6 @@ _TCA-0305_
786789
- `get_malware_family`
787790
- Returns all malware families to which sample belongs based on the detections from the latest AV scan
788791

789-
#### Class:
790-
```python
791-
class ExpressionSearch(TiCloudAPI)
792-
```
793-
_TCA-0306_
794-
#### Methods:
795-
- `search`
796-
- Returns a list of aggregated results from Titanium Cloud system that matches requested criteria
797-
- `get_latest_expression`
798-
- Returns a list of latest aggregated results from Titanuium Cloud system that matches requested criteria
799-
- `statistics_search`
800-
- Returns aggregated statistics about new samples in Titainum Cloud system that matches requested criteria
801-
- `get_latest_statistics`
802-
- Returns aggregated latest statistics about new samples in Titanium Cloud system that matches requested criteria
803-
804792
#### Class:
805793
```python
806794
class VerticalFeedsStatistics(TiCloudAPI)

ReversingLabs/SDK/ticloud.py

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,7 @@ def search_aggregated(self, query_string, sorting_criteria=None, sorting_order="
12151215
class ExpressionSearch(TiCloudAPI):
12161216
"""TCA-0306 - Expression Search with Statistics (Sample Search)"""
12171217

1218-
__EXPRESSION_QUERY_ENDPOINT = "/api/sample/search/download/v1/query/{time_format}/{time_value}"
1218+
__EXPRESSION_QUERY_ENDPOINT = "/api/sample/search/download/v1/query/date/{str_date}"
12191219
__LATEST_EXPRESSION_ENDPOINT = "/api/sample/search/download/v1/query/latest"
12201220
__STATISTICS_QUERY_ENDPOINT = "/api/sample/search/download/v1/statistics/{time_format}/{time_value}"
12211221
__LATEST_STATISTICS_QUERY_ENDPOINT = "/api/sample/search/download/v1/statistics/latest"
@@ -1227,7 +1227,7 @@ def __init__(self, host, username, password, verify=True, proxies=None, user_age
12271227

12281228
self._url = "{host}{{endpoint}}".format(host=self._host)
12291229

1230-
def search(self, query, time_format, time_value, page_number=1):
1230+
def search(self, query, date=None, page_number=1):
12311231
"""Sends the query to the Expression Search API.
12321232
The query must be a list of at least 2 strings. Each string is in the form of a key and a value with
12331233
an equals sign between them and no spaces.
@@ -1247,39 +1247,13 @@ def search(self, query, time_format, time_value, page_number=1):
12471247
12481248
:param query: search query
12491249
:type query: list
1250-
:param time_format: possibles values 'utc' or 'timestamp', 'date'
1251-
:type time_format: str
1252-
:param time_value: results will be retrieved from the specified date
1253-
:type time_value: str
1250+
:param date: return results from this date forward. the accepted date format is YYYY-mm-dd
1251+
:type date: str or any
12541252
:param page_number: page number
12551253
:type page_number: int
12561254
:return: response
12571255
:rtype: requests.Response
12581256
"""
1259-
if time_format == "timestamp":
1260-
try:
1261-
int(time_value)
1262-
1263-
except ValueError:
1264-
raise WrongInputError("if timestamp is used, time_value needs to be a unix timestamp")
1265-
1266-
elif time_format == "utc":
1267-
try:
1268-
datetime.datetime.strptime(time_value, "%Y-%m-%dT%H:%M:%S")
1269-
1270-
except ValueError:
1271-
raise WrongInputError("if utc is used, time_value needs to be in format 'YYYY-MM-DDThh:mm:ss'")
1272-
1273-
elif time_format == "date":
1274-
try:
1275-
datetime.datetime.strptime(time_value, "%Y-%m-%d")
1276-
1277-
except ValueError:
1278-
raise WrongInputError("if the date format is used, time_value must be provided as 'YYYY-MM-DD'")
1279-
1280-
else:
1281-
raise WrongInputError("time_format parameter must be one of the following: 'timestamp', 'utc', 'date'")
1282-
12831257
if not isinstance(query, list):
12841258
raise WrongInputError("query parameter must be a list of strings.")
12851259

@@ -1294,29 +1268,85 @@ def search(self, query, time_format, time_value, page_number=1):
12941268
if not isinstance(page_number, int):
12951269
raise WrongInputError("page_number parameter must be integer.")
12961270

1297-
base = self.__EXPRESSION_QUERY_ENDPOINT.format(
1298-
time_format=time_format,
1299-
time_value=time_value
1271+
if not date:
1272+
date = datetime.date.today() - datetime.timedelta(days=1)
1273+
if isinstance(date, str):
1274+
date = datetime.datetime.fromisoformat(date)
1275+
1276+
str_date = date.strftime("%Y-%m-%d")
1277+
1278+
endpoint_base = self.__EXPRESSION_QUERY_ENDPOINT.format(
1279+
str_date=str_date
13001280
)
13011281

1302-
endpoint = "{base}?{query_expression}".format(
1303-
base=base,
1282+
parameters = "?format=json&page={page}&{query_expression}".format(
1283+
page=page_number,
13041284
query_expression=query_expression
13051285
)
13061286

1307-
query_params = {
1308-
"page": page_number,
1309-
"format": "json"
1310-
}
1287+
endpoint = "{base}{params}".format(
1288+
base=endpoint_base,
1289+
params=parameters
1290+
)
13111291

13121292
url = self._url.format(endpoint=endpoint)
13131293

1314-
response = self._get_request(url=url, params=query_params)
1315-
1294+
response = self._get_request(url=url)
13161295
self._raise_on_error(response)
13171296

13181297
return response
13191298

1299+
def search_aggregated(self, query, date=None, max_results=5000):
1300+
"""Sends the query to the Expression Search API.
1301+
The query must be a list of at least 2 strings. Each string is in the form of a key and a value with
1302+
an equals sign between them and no spaces.
1303+
The value can have multiple options separated with a pipe symbol.
1304+
This method returns a list of aggregated results with a maximum length defined in the 'max_results' parameter.
1305+
Query examples:
1306+
1307+
['status=MALICIOUS',
1308+
'sample_type=MicrosoftWord|MicrosoftExcel|MicrosoftPowerPoint']
1309+
1310+
or
1311+
1312+
['threat_level>=3',
1313+
'status=malicious',
1314+
'malware_family=CVE-2017-11882']
1315+
1316+
:param query: search query
1317+
:type query: list
1318+
:param date: return results from this date forward
1319+
:type date: str or any
1320+
:param max_results: maximum results to be returned in the list; default value is 5000
1321+
:type max_results: int
1322+
:return: list of results
1323+
:rtype: list
1324+
"""
1325+
if not isinstance(max_results, int):
1326+
raise WrongInputError("max_results parameter must be integer.")
1327+
1328+
results = []
1329+
next_page = 1
1330+
1331+
while next_page:
1332+
response = self.search(
1333+
query=query,
1334+
date=date,
1335+
page_number=next_page
1336+
)
1337+
1338+
response_json = response.json()
1339+
1340+
entries = response_json.get("rl").get("web_sample_search_download").get("entries", [])
1341+
results.extend(entries)
1342+
1343+
next_page = response_json.get("rl").get("web_sample_search_download").get("next_page", None)
1344+
1345+
if len(results) >= max_results:
1346+
break
1347+
1348+
return results[:max_results]
1349+
13201350
def get_latest_expression(self, query):
13211351
"""Service returns only new samples from the last 24 hours.
13221352
The query must be a list of at least 2 strings. Each string is in the form of a key and a value with

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
setup(
1313
name="reversinglabs-sdk-py3",
14-
version="2.4.1",
14+
version="2.4.2",
1515
description="Python SDK for using ReversingLabs services - Python 3 version.",
1616
long_description=long_description,
1717
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)