Skip to content

Commit 914272e

Browse files
Merge branch 'develop' into 'main'
develop into main See merge request integrations/sdk/reversinglabs-sdk-py3!32
2 parents bbc38c3 + dd1ee50 commit 914272e

File tree

8 files changed

+1256
-106
lines changed

8 files changed

+1256
-106
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,28 @@ v2.5.1 (2024-04-02)
454454

455455

456456

457+
2.11.0 (2025-10-01)
458+
-------------------
459+
460+
#### Improvements
461+
- **a1000** module:
462+
- Android12 is now supported ass a dynamic analysis platform.
463+
- Added `get_yara_repositories`, `get_yara_repositories_aggregated`, `create_yara_repository`, `update_yara_repository`, `delete_yara_repository`, `publish_all_yara_rulesets`, `publish_single_yara_ruleset`, `set_yara_update_interval`, `reset_yara_update_interval` and `run_yara_update` functions.
464+
465+
- **ticloud** module:
466+
- Added bulk report support for `URLThreatIntelligence`, `DomainThreatIntelligence` and `IPThreatIntelligence`.
467+
- Added archive passwords support for `FileUpload`.
468+
- Added the `IocDataRetrieval` class.
469+
470+
- **advanced** module:
471+
- Added the `download_yara_matches` method in the `AdvancedActions` class.
472+
473+
474+
### ReversingLabs SDK Cookbook changes
475+
### Improvements
476+
- **Scenarios and Workflows** notebooks:
477+
- Fixed minor issues in the `advanced_search_using_network_indicators.ipynb` notebook.
478+
457479

458480

459481
-------------------

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,19 @@ Service can be used to retrieve information about new malware samples from Rever
837837
- `feed_query`
838838
- Retruns information about new malware samples from ReversingLabs Targeted and Industry-Specific File Indicator Feeds by searching for malware family names based on time when they are added to a particular feed
839839

840+
#### Class:
841+
```python
842+
class IocDataRetrieval(TiCloudAPI)
843+
````
844+
_TCA-0330_
845+
#### Methods:
846+
- `get_ioc_summary`
847+
- Accepts an IoC type and time range alongside filters and returns data summary for the filtered IoCs.
848+
- `get_latest_iocs`
849+
- Accepts an IoC type alongside filters and returns a data for all the latest IoCs. Results can be paginated.
850+
- `get_iocs_timerange`
851+
- Accepts an IoC type and time range alongside filters and returns data for the filtered IoCs. Results can be paginated
852+
840853
#### Class:
841854
```python
842855
class NetworkReputationUserOverride(TiCloudAPI)

ReversingLabs/SDK/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
A Python SDK for communicating with ReversingLabs services.
66
"""
77

8-
__version__ = "2.10.0"
8+
__version__ = "2.11.0"

ReversingLabs/SDK/a1000.py

Lines changed: 293 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
CLASSIFICATIONS = ("MALICIOUS", "SUSPICIOUS", "GOODWARE", "UNKNOWN")
23-
AVAILABLE_PLATFORMS = ("windows7", "windows10", "macos_11", "windows11", "linux")
23+
AVAILABLE_PLATFORMS = ("windows7", "windows10", "macos_11", "windows11", "linux", "android12")
2424

2525

2626
class A1000(object):
@@ -64,6 +64,12 @@ class A1000(object):
6464
__GET_OR_SET_YARA_RULESET_SYNCHRONIZATION_TIME_ENDPOINT = "/api/yara/ticloud/time/"
6565
__YARA_LOCAL_RETROSCAN_ENDPOINT = "/api/uploads/local-retro-hunt/"
6666
__YARA_CLOUD_RETROSCANS_ENDPOINT = "/api/yara/ruleset/{ruleset_name}/cloud-retro-hunt/"
67+
__YARA_REPOSITORIES_ENDPOINT = "/api/yara/repositories/"
68+
__YARA_PUBLISH_ALL_ENDPOINT = "/api/yara/publish/all/"
69+
__YARA_PUBLISH_RULESET_ENDPOINT = "/api/yara/publish/ruleset/{ruleset_name}/"
70+
__YARA_SET_UPDATE_ENDPOINT = "/api/yara/update/set-interval/{value_seconds}/"
71+
__YARA_RESET_UPDATE_ENDPOINT = "/api/yara/update/reset-interval/"
72+
__YARA_RUN_UPDATE_ENDPOINT = "/api/yara/update/run/"
6773
__ADVANCED_SEARCH_ENDPOINT_V2 = "/api/samples/v2/search/"
6874
__ADVANCED_SEARCH_ENDPOINT_V3 = "/api/samples/v3/search/"
6975
__LIST_CONTAINERS_ENDPOINT = "/api/samples/containers/"
@@ -2019,6 +2025,275 @@ def get_yara_cloud_retro_scan_status(self, ruleset_name):
20192025

20202026
return response
20212027

2028+
def publish_all_yara_rulesets(self):
2029+
"""Triggers publishing process for all non-core YARA rulesets in the system.
2030+
:return: response
2031+
:rtype: requests.Response
2032+
"""
2033+
url = self._url.format(endpoint=self.__YARA_PUBLISH_ALL_ENDPOINT)
2034+
2035+
response = self.__post_request(url=url)
2036+
2037+
self.__raise_on_error(response)
2038+
2039+
return response
2040+
2041+
def publish_single_yara_ruleset(self, ruleset_name):
2042+
"""Triggers publishing process for a single YARA ruleset in the system.
2043+
Only non-core rulesets can be published.
2044+
:param ruleset_name: Name of the ruleset
2045+
:type ruleset_name: str
2046+
:return: response
2047+
:rtype: requests.Response
2048+
"""
2049+
endpoint = self._url.format(endpoint=self.__YARA_PUBLISH_RULESET_ENDPOINT)
2050+
2051+
url = endpoint.format(ruleset_name=ruleset_name)
2052+
2053+
response = self.__post_request(url=url)
2054+
2055+
self.__raise_on_error(response)
2056+
2057+
return response
2058+
2059+
def get_yara_repositories(self, active_filter=None, page_size=None, page_number=None):
2060+
"""Returns a list of all Yara Online Sources repositories
2061+
with optional filtering by custom or system repositories.
2062+
:param active_filter: Filter repositories by type. Possible values: 'all', 'user' and 'system'
2063+
:type active_filter: str
2064+
:param page_size: Number of results per page
2065+
:type page_size: int
2066+
:param page_number: Page number
2067+
:type page_number: int
2068+
:return: response
2069+
:rtype: requests.Response
2070+
"""
2071+
url = self._url.format(endpoint=self.__YARA_REPOSITORIES_ENDPOINT)
2072+
2073+
params = {
2074+
"active_filter": active_filter,
2075+
"page_size": page_size,
2076+
"page": page_number
2077+
}
2078+
2079+
response = self.__get_request(url=url, params=params)
2080+
2081+
self.__raise_on_error(response)
2082+
2083+
return response
2084+
2085+
def get_yara_repositories_aggregated(self, active_filter=None, page_size=100, max_results=None):
2086+
"""Returns a list of all Yara Online Sources repositories
2087+
with optional filtering by custom or system repositories.
2088+
This method performs automated paging with defining the maximum number of returned results being optional.
2089+
:param active_filter: Filter repositories by type. Possible values: 'all', 'user' and 'system'
2090+
:type active_filter: str
2091+
:param page_size: Number of results per page
2092+
:type page_size: int
2093+
:param max_results: Maximum number of results to return; Leave as None to get all results
2094+
:type max_results: int or None
2095+
:return: list of results
2096+
:rtype: List
2097+
"""
2098+
results_list = []
2099+
2100+
response = self.get_yara_repositories(
2101+
active_filter=active_filter,
2102+
page_size=page_size,
2103+
page_number=1
2104+
)
2105+
2106+
results = response.json().get("results", [])
2107+
results_list.append(results)
2108+
2109+
if max_results and len(results_list) >= max_results:
2110+
return results_list[:max_results]
2111+
2112+
next_page_url = response.json().get("next")
2113+
2114+
while next_page_url:
2115+
response = self.__get_request(url=next_page_url)
2116+
2117+
results = response.json().get("results", [])
2118+
results_list.append(results)
2119+
2120+
next_page_url = response.json().get("next")
2121+
2122+
if not max_results:
2123+
if not next_page_url:
2124+
break
2125+
2126+
else:
2127+
if not next_page_url or len(results_list) >= max_results:
2128+
return results_list[:max_results]
2129+
2130+
return results_list
2131+
2132+
def create_yara_repository(self, repository_url, name, source_branch, api_token, import_update_preferences):
2133+
"""Creates a new Yara Online Source repository for fetching and managing YARA rules.
2134+
The system will verify connectivity to the provided GitHub repository before creation.
2135+
:param repository_url: URL pointing to the remote ruleset repository.
2136+
:type repository_url: str
2137+
:param name: Display name for the repository
2138+
:type name: str
2139+
:param source_branch: Git branch to pull rulesets from; Defaults to main or master if omitted
2140+
:type source_branch: str
2141+
:param api_token: Token used to authenticate to private remote repositories
2142+
:type api_token: str
2143+
:param import_update_preferences: Integer enum representing importing update preferences;
2144+
Supported values: 0 - Manual, 1 - Auto-Update, 2 - Auto-Update and Auto-Import.
2145+
:type import_update_preferences: int
2146+
:return: response
2147+
:rtype: requests.Response
2148+
"""
2149+
args = {
2150+
"repository_url": repository_url,
2151+
"name": name,
2152+
"source_branch": source_branch,
2153+
"api_token": api_token,
2154+
}
2155+
2156+
for arg, value in args.items():
2157+
if not isinstance(value, str):
2158+
raise WrongInputError(f"{arg} needs to be a string")
2159+
2160+
if not isinstance(import_update_preferences, int):
2161+
raise WrongInputError(f"import_update_preferences needs to be an integer")
2162+
2163+
url = self._url.format(endpoint=self.__YARA_REPOSITORIES_ENDPOINT)
2164+
2165+
post_json = {"url": repository_url, "name": name, "source_branch": source_branch, "api_token": api_token,
2166+
"import_update_preferences": import_update_preferences}
2167+
2168+
response = self.__post_request(url=url, post_json=post_json)
2169+
2170+
self.__raise_on_error(response)
2171+
2172+
return response
2173+
2174+
def update_yara_repository(self, repository_id, repository_url, name, source_branch, api_token,
2175+
import_update_preferences):
2176+
"""Updates a Yara Online Source repository.
2177+
The system will verify connectivity to the provided GitHub repository before updating.
2178+
:param repository_id: Repository ID
2179+
:type repository_id: int
2180+
:param repository_url: URL pointing to the remote ruleset repository.
2181+
:type repository_url: str
2182+
:param name: Display name for the repository
2183+
:type name: str
2184+
:param source_branch: Git branch to pull rulesets from; Defaults to main or master if omitted
2185+
:type source_branch: str
2186+
:param api_token: Token used to authenticate to private remote repositories
2187+
:type api_token: str
2188+
:param import_update_preferences: Integer enum representing importing update preferences;
2189+
Supported values: 0 - Manual, 1 - Auto-Update, 2 - Auto-Update and Auto-Import.
2190+
:type import_update_preferences: int
2191+
:return: response
2192+
:rtype: requests.Response
2193+
"""
2194+
args = {
2195+
"repository_url": repository_url,
2196+
"name": name,
2197+
"source_branch": source_branch,
2198+
"api_token": api_token,
2199+
}
2200+
2201+
for arg, value in args.items():
2202+
if not isinstance(value, str):
2203+
raise WrongInputError(f"{arg} needs to be a string")
2204+
2205+
if not isinstance(import_update_preferences, int):
2206+
raise WrongInputError(f"import_update_preferences needs to be an integer")
2207+
2208+
if not isinstance(repository_id, int):
2209+
raise WrongInputError("repository_id needs to be an integer")
2210+
2211+
endpoint = f"{self.__YARA_REPOSITORIES_ENDPOINT}/{repository_id}/"
2212+
2213+
url = self._url.format(endpoint=endpoint)
2214+
2215+
put_json = {"url": repository_url, "name": name, "source_branch": source_branch, "api_token": api_token,
2216+
"import_update_preferences": import_update_preferences}
2217+
2218+
response = self.__put_request(url=url, put_json=put_json)
2219+
2220+
self.__raise_on_error(response)
2221+
2222+
return response
2223+
2224+
def delete_yara_repository(self, repository_id, remove_rulesets=False):
2225+
"""Deletes a Yara Online Source repository. Only custom repositories can be deleted,
2226+
and only by their owner or a superuser. Associated rulesets can optionally be removed as well.
2227+
:param repository_id: Repository ID
2228+
:type repository_id: int
2229+
:param remove_rulesets: Whether to also remove all rulesets associated with this repository;
2230+
Default is False
2231+
:type remove_rulesets: bool
2232+
:return: response
2233+
:rtype: requests.Response
2234+
"""
2235+
if not isinstance(remove_rulesets, bool):
2236+
raise WrongInputError("remove_rulesets needs to be boolean")
2237+
2238+
endpoint = f"{self.__YARA_REPOSITORIES_ENDPOINT}/{repository_id}/"
2239+
2240+
url = self._url.format(endpoint=endpoint)
2241+
2242+
params = {"shouldRemoveRulesets": remove_rulesets}
2243+
2244+
response = self.__delete_request(url=url, params=params)
2245+
2246+
self.__raise_on_error(response)
2247+
2248+
return response
2249+
2250+
def set_yara_update_interval(self, seconds):
2251+
"""Configure the interval at which the YARA update job runs automatically.
2252+
:param seconds: Interval in seconds;
2253+
Setting to '0' will disable auto-update job (This will introduce a short maintenance downtime)
2254+
:type seconds: int
2255+
:return: response
2256+
:rtype: requests.Response
2257+
"""
2258+
if not isinstance(seconds, int):
2259+
raise WrongInputError("seconds should be an integer")
2260+
2261+
endpoint = self.__YARA_SET_UPDATE_ENDPOINT.format(value_seconds=seconds)
2262+
2263+
url = self._url.format(endpoint=endpoint)
2264+
2265+
response = self.__post_request(url=url)
2266+
2267+
self.__raise_on_error(response)
2268+
2269+
return response
2270+
2271+
def reset_yara_update_interval(self):
2272+
"""Reset the YARA update job cadence to its default value.
2273+
:return: response
2274+
:rtype: requests.Response
2275+
"""
2276+
url = self._url.format(endpoint=self.__YARA_RESET_UPDATE_ENDPOINT)
2277+
2278+
response = self.__post_request(url=url)
2279+
2280+
self.__raise_on_error(response)
2281+
2282+
return response
2283+
2284+
def run_yara_update(self):
2285+
"""Manually trigger YARA update job execution.
2286+
:return: response
2287+
:rtype: requests.Response
2288+
"""
2289+
url = self._url.format(endpoint=self.__YARA_RUN_UPDATE_ENDPOINT)
2290+
2291+
response = self.__post_request(url=url)
2292+
2293+
self.__raise_on_error(response)
2294+
2295+
return response
2296+
20222297
def advanced_search_v2(self, query_string, ticloud=False, page_number=1, records_per_page=20, sorting_criteria=None,
20232298
sorting_order="desc"):
20242299
"""THIS METHOD IS DEPRECATED. Use advanced_search_v3 instead.
@@ -2852,7 +3127,7 @@ def __post_request(self, url, post_json=None, files=None, data=None, params=None
28523127

28533128
return response
28543129

2855-
def __delete_request(self, url, post_json=None):
3130+
def __delete_request(self, url, post_json=None, params=None):
28563131
"""A generic DELETE request method for all A1000 methods.
28573132
:param url: request URL
28583133
:type url: str
@@ -2865,6 +3140,22 @@ def __delete_request(self, url, post_json=None):
28653140
response = requests.delete(
28663141
url=url,
28673142
json=post_json,
3143+
params=params,
3144+
verify=self._verify,
3145+
proxies=self._proxies,
3146+
headers=self._headers
3147+
)
3148+
3149+
return response
3150+
3151+
def __put_request(self, url, data=None, put_json=None):
3152+
self._headers["User-Agent"] = (f"{self._user_agent}; "
3153+
f"{self.__class__.__name__} {inspect.currentframe().f_back.f_code.co_name}")
3154+
3155+
response = requests.put(
3156+
url=url,
3157+
data=data,
3158+
json=put_json,
28683159
verify=self._verify,
28693160
proxies=self._proxies,
28703161
headers=self._headers

0 commit comments

Comments
 (0)