Navigation
Analysis Client Shell¶
The Analysis Client Shell allows running the client from the command line. This provides an interactive shell for manually sending requests to the Lastline Analyst API, and it can be used to experiment with the API for analyzing files or URLs.
This client shell is available for download at analysis_apiclient_shell.py.
To start the shell, place the file in the same directory as the Analyst API client and invoke:
python analysis_apiclient_shell.py <API_KEY> <API_TOKEN>
replacing <API_KEY> and <API_TOKEN> with your API credentials. Once the shell is started, the
current context contains an analysis
object. This is an instance of
analysis_apiclient.AnalysisClient
, which can be used to access the
functionality of the Lastline Analyst API.
By default, the client connects to an API instance running in the hosted Lastline datacenters
at https://analysis.lastline.com . To connect to a different instance, for
example when using a Lastline On-Premises installation, please use the
--api-url
parameter to point to the URL of the On-Premises API. For
example, to
connect to a Lastline Analyst On-Premises running at analyst.lastline.local,
use:
python analysis_apiclient_shell.py --api-url https://analyst.lastline.local/ <API_KEY> <API_TOKEN>
Analyst API Shell Example¶
This is an example of how the Analyst API shell can be used to analyze resources with the Lastline Analyst API:
$ python analysis_apiclient_shell.py XXXXXXXXXXXXXXXXXXXX yyyyyyyyyyyyyyyyyyyyyyyyy
-------------------------------------
Lastline Analyst API shell
-------------------------------------
[...]
In [1]:
Here, XXX and yyy need to be replaced with your API key and token respectively.
IPython features make the API shell easy to discover. For instance, you can use tab-autocompletion to list the methods of an object:
In [1]: analysis.
analysis.DATETIME_FMT analysis.get_completed analysis.submit_exe_file
analysis.DATE_FMT analysis.get_completed_with_metadata analysis.submit_exe_hash
analysis.ERRORS analysis.get_progress analysis.submit_file
analysis.FORMATS analysis.get_result analysis.submit_file_hash
analysis.SUB_APIS analysis.get_result_artifact analysis.submit_file_metadata
analysis.analyze_sandbox_result analysis.get_result_summary analysis.submit_url
analysis.completed analysis.rescore_task analysis.set_key
And you can use the question-mark character after an object or method to get documentation for it:
In [2]: analysis.submit_file?
...
File: analysis_apiclient.py
Definition: analysis.submit_file(self, file_stream, download_ip=None, download_port=None, download_url=None, download_host=None, download_path=None, download_agent=None, download_referer=None, download_request=None, full_report_score=None, bypass_cache=False, delete_after_analysis=False, backend=None, raw=False, verify=True)
Docstring:
Submit a file by uploading it.
For return values and error codes please
see :py:meth:`malscape.api.views.analysis.submit_file`.
If there is an error and `raw` is not set,
a :py:class:`AnalysisAPIError` exception will be raised.
:param file_stream: file-like object containing
the file to upload.
:param download_ip: ASCII dotted-quad representation of the IP address
from which the file has been downloaded
:param download_port: integer representation of the port number
from which the file has been downloaded
:param download_url: DEPRECATED! replaced by the download_host
and download_path parameters
:param download_host: host from which the submitted file
was originally downloaded, as a string of bytes (not unicode)
:param download_path: host path from which the submitted file
was originally downloaded, as a string of bytes (not unicode)
:param download_agent: HTTP user-agent header that was used
when the submitted file was originally downloaded,
as a string of bytes (not unicode)
:param download_referer: HTTP referer header that was used
when the submitted file was originally downloaded,
as a string of bytes (not unicode)
:param download_request: full HTTP request with
which the submitted file was originally downloaded,
as a string of bytes (not unicode)
:param full_report_score: if set, this value (between -1 and 101)
determines starting at which scores a full report is returned.
-1 and 101 indicate "never return full report";
0 indicates "return full report at all times"
:param bypass_cache: if True, the API will not serve a cached
result. NOTE: This requires special privileges. Further, this
only bypasses the MalScape caching and might still serve a
cached result from the analysis engine
:param delete_after_analysis: if True, the backend will delete the
file after analysis is done (and noone previously submitted
this file with this flag set)
:param backend: DEPRECATED! Don't use
:param verify: if False, disable SSL-certificate verification
:param raw: if True, return the raw JSON results of the API query
The same documentation is also available above.
Let’s see how we can submit a URL:
In [3]: r=analysis.submit_url("http://www.google.com")
In [4]: r
Out[4]: {u'data': {u'task_uuid': u'a553401f249f4c209541799c3ccede23'}, u'success': 1}
uuid=r["data"]["task_uuid"]
We now have a UUID for the submitted analysis task. We can use this to request the results:
In [5]: result=analysis.get_result(uuid)
In [6]: result["data"]["score"]
Out[6]: 0
In [7]: import pprint
In [8]: pprint.pprint(result["data"]["report"])
{u'analysis': {u'artifacts': None,
u'et_results': None,
u'exploits': None,
u'network': {u'redirects': None,
u'requests': [{u'content_md5': u'6294aa8e1853ee626776e7c8c9e6ff3b',
u'content_sha1': None,
u'content_type': u'text/html',
u'ip': None,
u'parent_url': u'USER_URL',
u'relation_type': u'6',
u'status': u'302',
u'url': u'http://www.google.com/'},
{u'content_md5': u'd0d9aabed132ac09e529453b7e26b864',
u'content_sha1': u'b680dab114a88277cd7364dbdc06bac358fa0eb8',
u'content_type': u'text/html',
u'ip': u'74.125.224.191',
u'parent_url': u'http://www.google.com/',
u'relation_type': u'4',
u'status': u'200',
u'url': u'http://www.google.co.uk/'},
{u'content_md5': u'cceaec1808de4b7c9e7a0df5c213db74',
u'content_sha1': u'd5c0854c026fd1ed9b15b32bae87dc73723cbd6e',
u'content_type': u'text/javascript',
u'ip': u'74.125.224.191',
u'parent_url': u'http://www.google.co.uk/',
u'relation_type': u'1',
u'status': u'200',
u'url': u'http://www.google.co.uk/xjs/_/js/k=xjs.hp.en_US.vpRA07Px3nw.O/m=sb_he,pcc/rt=j/d=1/sv=1/rs=AItRSTM2fR5rzErXQEgClenaRaSY3BLhMw'},
{u'content_md5': u'a0af21c60b0dddc27b96d9294b7d5d8f',
u'content_sha1': u'88ae1a2683797b31dc07d7128d7a24a628bdf52e',
u'content_type': u'text/javascript',
u'ip': u'74.125.239.15',
u'parent_url': u'http://www.google.co.uk/',
u'relation_type': u'1',
u'status': u'200',
u'url': u'http://ssl.gstatic.com/gb/js/sem_a0af21c60b0dddc27b96d9294b7d5d8f.js'}]},
u'plugins': None,
u'result': {u'analysis_ended': u'2013-07-09 04:44:51+0000',
u'classification': u'benign',
u'detector': u'2.6',
u'explanation': u'models:0.84:0.00:0.84:0.00:0.00'},
u'shellcodes': None,
u'signatures': None,
u'subject': {u'type': u'url',
u'url': u'http://www.google.com/'},
u'threats': None},
...
The score is 0 since this is a benign site. The report contains more information.
Submitting an executable for analysis is similar, except that we can first check to see if the executable is already known by submitting a hash:
In [21]: f=open('example.file')
In [22]: import hashlib
In [23]: md5=hashlib.md5(f.read()).hexdigest()
In [24]: md5
Out[24]: 'f19e59513a23e676495fa72bd97995f2'
In [25]: analysis.submit_file_hash(md5=md5)
---------------------------------------------------------------------------
AnalysisAPIError Traceback (most recent call last)
<ipython console> in <module>()
analysis_apiclient.py in submit_file_hash(self, md5, sha1, download_url, download_request, download_referer, full_report_score, bypass_cache, raw)
215 purge_none(files)
216 purge_none(params)
--> 217 return self._api_request(url, params, files=files, post=True, raw=raw)
218
219 def submit_file(self,
analysis_apiclient.py in _api_request(self, url, params, files, timeout, post, raw, requested_format)
423 response.raise_for_status()
424 page = response.text
--> 425 return self._process_response_page(page, raw, requested_format)
426
427 def init_shell(banner):
analysis_apiclient.py in _process_response_page(self, page, raw, requested_format)
362 else:
363 error_code = result.get('error_code', None)
--> 364 raise AnalysisAPIError(result['error'], error_code)
365
366 class AnalysisClient(AnalysisClientBase):
AnalysisAPIError: Analysis API error (101): No file found matching requested hash.
In this case, the hash was not previously known, so an exception is raised. We should instead submit the file:
In [26]: f.seek(0)
In [27]: analysis.submit_file(f)
Out[27]: {u'data': {u'task_uuid': u'1483924fa75c440ab5493b1557ab57c5'}, u'success': 1}
Analyst API Shell Helpers¶
The API client module provides helper classes for submitting artifacts and waiting for analysis results:
In [28]: import analysis_apiclient
In [29]: helper = analysis_apiclient.SubmissionHelper(analysis)
In [30]: ts = helper.get_api_utc_timestamp()
In [31]: result = helper.submit_url('https://www.lastline.com/')
Submitting URL https://www.lastline.com/
In [32]: result.is_complete()
Out[32]: False
In [33]: helper.wait_for_completion_of_submission(result, ts)
Waiting for completion of 1/1 submissions
Waiting for completion of 1 submissions
Got result for task 86a5fe607aaa4181867ed9a71bcab664
Got result for task 86a5fe607aaa4181867ed9a71bcab664: AnalysisTask 86a5fe607aaa4181867ed9a71bcab664(score: 1): URL=https://www.lastline.com/
Done waiting for completion of 1 submissions
In [34]: result.is_complete()
Out[34]: True
In [35]: result.task_uuid
Out[35]: u'86a5fe607aaa4181867ed9a71bcab664'
In [36]: result.score
Out[36]: 1
In [37]: result = helper.submit_filename('./myfile.exe')
In [38]: result.is_complete()
Out[38]: True
In [39]: result.score
Out[39]: 95
In [40]: helper.wait_for_completion_of_submission(result, ts)
No need to wait for completion for any of 1 submissions
These helper classes also support submitting multiple artifacts at once:
In [41]: results = helper.submit_urls_and_wait_for_completion(['http://www.google.com', 'https://www.lastline.com'])
Submitting 2 URLs
Submitting URL http://www.google.com
Submitting URL https://www.lastline.com
Waiting for completion of 0/2 submissions
Done waiting for completion of 2 submissions
In [42]: results['https://www.lastline.com'].task_uuid
Out[42]: u'0ea874bab6ac4c58b374087cc0047cfb'
In [43]: results['https://www.lastline.com'].score
Out[43]: 1
In [44]: results = helper.submit_filenames_and_wait_for_completion([ './sample.exe', './hello.exe' ], bypass_cache=True)
Submitting 2 files
Submitting file hello.exe (md5=ac8545103e85219d7ff98bcd5f5a12ff, sha1=969f7183dc81eb7e95e47f06d640623be4f5ed9f)
Submitting file by hash failed: Analysis API error (101): No file found matching requested hash.
Submitting file sample.exe (md5=e7742a9e48739ce3abb686c2c1299690, sha1=b58cd7d734f9b8deb93c0f52bec8437c3c1b99ca)
Submitting file by hash failed: Analysis API error (101): No file found matching requested hash.
Waiting for completion of 2/2 submissions
[...]
Got result for task dfde1ddce80b48488034bcaaf29bc6ab
Got result for task dfde1ddce80b48488034bcaaf29bc6ab: AnalysisTask dfde1ddce80b48488034bcaaf29bc6ab: MD5=ac8545103e85219d7ff98bcd5f5a12ff, SHA1=969f7183dc81eb7e95e47f06d640623be4f5ed9f, name=hello.exe
Waiting for completion of 1 submissions
Got result for task b735251ecd44475cad34fb6a20a1d4cf
Got result for task b735251ecd44475cad34fb6a20a1d4cf: AnalysisTask b735251ecd44475cad34fb6a20a1d4cf(score: 19): MD5=e7742a9e48739ce3abb686c2c1299690, SHA1=b58cd7d734f9b8deb93c0f52bec8437c3c1b99ca, name=sample.exe
Done waiting for completion of 2 submissions
In [45]: results['./sample.exe'].task_uuid
Out[45]: u'b735251ecd44475cad34fb6a20a1d4cf'
In [46]: results['./sample.exe'].score
Out[46]: 19
Furthermore, the API shell also provides helper classes for accessing analysis data, such as the primary analysis subject. To download the file using the task UUID (assuming it is available in the system), use:
In [1]: import analysis_apiclient
In [2]: helper = analysis_apiclient.QueryHelper(analysis)
In [3]: file_stream = helper.download_analysis_subject_file(task_uuid=<task-UUID>)
In [4]: if file_stream:
...: print 'Download successful, downloaded file has SHA1',
...: print analysis_apiclient.hash_stream(file_stream, 'sha1')
Download successful, downloaded file has SHA1 <analysis-subject-SHA1>
Similarly, if the file hash of the analysis subject is known, the file can be downloaded using:
In [1]: import analysis_apiclient
In [2]: helper = analysis_apiclient.QueryHelper(analysis)
In [3]: file_stream = helper.download_analysis_subject_by_file_hash(sha1=<sha1>)
In [4]: with open(<output-filename>, 'wb') as outf:
...: outf.write(file_stream.read())
Note that these helper classes are automatically instantiated and provided to the user when
using the API client shell analysis_apiclient_shell.py
:
$ python analysis_apiclient_shell.py <key> <token>
--------------------------------------
Lastline Analyst API shell
--------------------------------------
[...]
In [1]: result = submission_helper.submit_url('https://www.lastline.com/')
In [2]: file_stream = query_helper.download_analysis_subject_by_file_hash(md5=<md5>)