diff --git a/README.md b/README.md index 1c4ec65..e7372f6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# API_Examples -Usage examples of AnyRun API +# API Examples +This repository contains some usage examples of AnyRun API + +# Preparations: +`pip install -r requirements.txt` + +# Examples: +- [Uploading zipped sample to AnyRUN, unpacking it and running it afterwards](https://github.com/h4rdee/API_Examples/tree/main/examples/upload-unpack-run) +- [Mass sample uploader](https://github.com/h4rdee/API_Examples/tree/main/examples/mass-uploader) diff --git a/examples/mass-uploader/README.md b/examples/mass-uploader/README.md new file mode 100644 index 0000000..657ba4d --- /dev/null +++ b/examples/mass-uploader/README.md @@ -0,0 +1,5 @@ +# Usage +- Put samples that you need to analyze to `/samples` folder +- Edit `config.json`, add your API token and edit task parameters (more info can be found [here](https://any.run/api-documentation/#api-Analysis-PostAnalysis)) +- `python main.py` +- Reports can be found in this directory (`_report.json`) diff --git a/examples/mass-uploader/analyzer.py b/examples/mass-uploader/analyzer.py new file mode 100644 index 0000000..dfbdcfd --- /dev/null +++ b/examples/mass-uploader/analyzer.py @@ -0,0 +1,38 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from anyrun import AnyRunClient +from config import Config + +class SamplesAnalyzer: + def __init__(self, cwd: str, config: Config) -> None: + self.cwd = cwd + self.anyrun_client = AnyRunClient(config) + + def analyze_all(self) -> bool: + samples_path = os.path.join(self.cwd, "samples") + with open("result.log", 'w') as log: + log.write("[>] starting test\n") + for filename in os.listdir(samples_path): + file = os.path.join(samples_path, filename) + if os.path.isfile(file): + print(f"[>] analyzing {filename}..") + if self.anyrun_client.perform_analysis(file) == True: + log.write( + f"\n[+] saved report as {filename}.json" + f"\ntask link: https://app.any.run/tasks/{self.anyrun_client.get_last_task_uuid()}\n" + ) + else: + log.write(f"\n[-] failed to save report for {filename}\n") diff --git a/examples/mass-uploader/anyrun.py b/examples/mass-uploader/anyrun.py new file mode 100644 index 0000000..a4f4508 --- /dev/null +++ b/examples/mass-uploader/anyrun.py @@ -0,0 +1,140 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import requests, json, time, os + +from config import Config +from enum import IntEnum + +class EResponseCode(IntEnum): + OK = 200, + TASK_FAILED = 422, + LIMITS_EXCEEDED = 429, + TASK_DIDNT_STARTED = 500 + +class AnyRunClient: + REPORT_CHECK_DELAY = 10 + + def __init__(self, config: Config) -> None: + self.cfg_obj = config.get_obj() + self.token = config.get_token() + + def __validate_api_response(self, response): + if response.status_code == EResponseCode.LIMITS_EXCEEDED: # another task running? + if len(response.text) != 0: + api_resp = json.loads(response.text) + if "message" in api_resp: + if api_resp["message"] == "Limits exceeded": + print( + "[!] seems like there's another task " + "which already running (kill it) " + "or limits might be exceeded" + ) + return None + + if response.status_code == EResponseCode.TASK_FAILED: # task failed? + if len(response.text) != 0: + api_resp = json.loads(response.text) + if "message" in api_resp: + if api_resp["message"] == "No content": + print( + "[!] error occuried while task " + "was running, no report available" + ) + return {'no_data': 'true'} + + if response.status_code == EResponseCode.TASK_DIDNT_STARTED: # task failed to start? + if len(response.text) != 0: + api_resp = json.loads(response.text) + if "error" in api_resp and "message" in api_resp: + if api_resp["error"] == True: + print(f"[!] task didn't start: {api_resp['message']}") + return {'no_data': 'true'} + + if response.status_code == EResponseCode.OK: # everything's fine + if len(response.text) != 0: + api_resp = json.loads(response.text) + if "error" in api_resp: + if api_resp["error"] == False: + return api_resp + + return None + + def __get_report(self, task_uuid: str): + api_req = requests.get( + f"https://api.any.run/v1/analysis/{task_uuid}", + headers={"Authorization": f"API-Key {self.token}"}, + ) + + api_resp = self.__validate_api_response(api_req) + + if api_resp == None: + return None + + if "data" in api_resp: + if "status" in api_resp["data"]: + if api_resp["data"]["status"] == "done": + return api_resp + else: + return None + + if "no_data" in api_resp: + return api_resp + + def get_last_task_uuid(self) -> str: + return self.last_task_uuid + + def perform_analysis(self, file_path) -> bool: + self.last_task_uuid = 0 + + with open(file_path, 'rb') as file: + api_req = requests.post( + f"https://api.any.run/v1/analysis", + files={'file': file.read()}, + headers={"Authorization": f"API-Key {self.token}"}, + data=self.cfg_obj["task_params"] + ) + + api_resp = self.__validate_api_response(api_req) + + if api_resp == None: + print(f"[-] failed to create a task! invalid response") + return False + + if "data" in api_resp: + if "taskid" in api_resp["data"]: + self.last_task_uuid = api_resp["data"]["taskid"] + + if self.last_task_uuid == 0: + print(f"[-] failed to create a task! api response: {api_req.text}") + return False + + print(f"[+] created task: {self.last_task_uuid}") + report_resp = None + + print(f"[>] waiting for report..") + + while report_resp == None: + report_resp = self.__get_report(self.last_task_uuid) + if report_resp != None: + if "no_data" in report_resp: + return False + time.sleep(self.REPORT_CHECK_DELAY) + + print(f"[+] task finished! saving report..") + + _, filename = os.path.split(file_path) + with open(f"{filename}_report.json", 'w') as file: + file.write(json.dumps(report_resp)) + + return True diff --git a/examples/mass-uploader/config.json b/examples/mass-uploader/config.json new file mode 100644 index 0000000..6072c56 --- /dev/null +++ b/examples/mass-uploader/config.json @@ -0,0 +1,14 @@ +{ + "token": "", + "task_params": { + "env_os": "windows", + "env_bitness": 64, + "env_version": "7", + "env_type": "complete", + "opt_network_connect": false, + "opt_privacy_type": "bylink", + "obj_ext_startfolder": "desktop", + "obj_ext_elevateprompt": false, + "opt_timeout": 60 + } +} diff --git a/examples/mass-uploader/config.py b/examples/mass-uploader/config.py new file mode 100644 index 0000000..134e830 --- /dev/null +++ b/examples/mass-uploader/config.py @@ -0,0 +1,50 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json, os + +class Config: + valid_config = False + cfg_obj = {} + + def __init__(self, cwd: str) -> None: + with open(os.path.join(cwd, "config.json"), encoding='utf-8') as json_cfg: + try: + self.cfg_obj = json.load(json_cfg) + self.valid_config = self.__validate_config() + + if self.valid_config: + print("[+] config loaded") + else: + print("[-] invalid config") + + except Exception as ex: + print(f"[!] exception: {ex}") + + def __validate_config(self) -> bool: + return all([ + "token" in self.cfg_obj, + "task_params" in self.cfg_obj + ]) + + def get_token(self) -> str: + if self.valid_config: + return self.cfg_obj["token"] + + def get_task_params(self) -> dict: + if self.valid_config: + return self.cfg_obj["task_params"] + + def get_obj(self) -> dict: + if self.valid_config: + return self.cfg_obj diff --git a/examples/mass-uploader/main.py b/examples/mass-uploader/main.py new file mode 100644 index 0000000..82153d3 --- /dev/null +++ b/examples/mass-uploader/main.py @@ -0,0 +1,29 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# TODO: +# - check API limits / parallels limit to create multiple tasks at once + +import os + +from config import Config +from analyzer import SamplesAnalyzer + +def main() -> None: + cwd = os.getcwd() # get cwd + cfg = Config(cwd) # load config + analyzer = SamplesAnalyzer(cwd, cfg) # initialize analyzer + analyzer.analyze_all() # batch-process samples + +if __name__ == "__main__": + main() diff --git a/UploadUnpackAndRun.py b/examples/upload-unpack-run/UploadUnpackAndRun.py similarity index 100% rename from UploadUnpackAndRun.py rename to examples/upload-unpack-run/UploadUnpackAndRun.py diff --git a/example/sample.zip b/examples/upload-unpack-run/example/sample.zip similarity index 100% rename from example/sample.zip rename to examples/upload-unpack-run/example/sample.zip diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file