Skip to content

Commit def665d

Browse files
authored
Merge pull request #6 from kevthehermit/master
update
2 parents 1e83a4c + e7faa51 commit def665d

File tree

10 files changed

+125
-81
lines changed

10 files changed

+125
-81
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ Pastehunter supports several output modules:
2222
- Dump to CSV file.
2323
- Send to syslog.
2424

25+
## Supported Sandboxes
26+
Pastehunter supports several sandboxes that decoded data can be sent to:
27+
- Cuckoo
28+
- Viper
29+
2530
For examples of data discovered using pastehunter check out my posts https://techanarchy.net/blog/hunting-pastebin-with-pastehunter and https://techanarchy.net/blog/pastehunter-the-results

docs/postprocess.rst

+1-14
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,7 @@ when the full paste is a base64 blob, i.e. it will not extract base64 code that
2929

3030
- **rule_list**: List of rules that will trigger the postprocess module.
3131

32-
33-
Cuckoo
34-
^^^^^^
35-
If the samples match a binary file format you can optionaly send the file for analysis by a Cuckoo Sandbox.
36-
37-
- **api_host**: IP or hostname for a Cuckoo API endpoint.
38-
- **api_port**: Port number for a Cuckoo API endpoint.
39-
40-
Viper
41-
^^^^^
42-
If the samples match a binary file format you can optionaly send the file to a Viper instance for further analysis.
43-
44-
- **api_host**: IP or hostname for a Cuckoo API endpoint.
45-
- **api_port**: Port number for a Cuckoo API endpoint.
32+
See the `Sandboxes documentation <sandboxes.rst>`_ for information on how to configure the sandboxes used for scanning decoded base64 data.
4633

4734

4835
Entropy

docs/sandboxes.rst

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Sandboxes
2+
=========
3+
4+
There are a few sandboxes that can be configured and used in various post process steps.
5+
6+
There are a few generic options for each input.
7+
8+
- **enabled**: This turns the sandbox on and off.
9+
- **module**: This is used internally by pastehunter.
10+
11+
Cuckoo
12+
------
13+
14+
If the samples match a binary file format you can optionaly send the file for analysis by a Cuckoo Sandbox.
15+
16+
- **api_host**: IP or hostname for a Cuckoo API endpoint.
17+
- **api_port**: Port number for a Cuckoo API endpoint.
18+
19+
Viper
20+
-----
21+
22+
If the samples match a binary file format you can optionaly send the file to a Viper instance for further analysis.
23+
24+
- **api_host**: IP or hostname for a Viper API endpoint.
25+
- **api_port**: Port number for a Viper API endpoint.

inputs/slexy.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class SlexySite(object):
1010

1111
def __init__(self):
1212
self.site = "slexy.org"
13-
url_slexy = "http://" + self.site
13+
url_slexy = "https://" + self.site
1414
self.url_recent = url_slexy + "/recent"
1515
self.url_view = url_slexy + "/view"
1616
self.url_raw = url_slexy + "/raw"
@@ -27,7 +27,7 @@ def create_req(self, url):
2727
data=None,
2828
headers={
2929
'Referer': self.url_recent,
30-
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36'
30+
'User-Agent': 'PasteHunter'
3131
}
3232
)
3333

pastehunter.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def paste_scanner():
157157
sleep(0.5)
158158
else:
159159
paste_data = q.get()
160-
with timeout(seconds=10):
160+
with timeout(seconds=conf['general']['process_timeout']):
161161
# Start a timer
162162
start_time = time.time()
163163
logger.debug("Found New {0} paste {1}".format(paste_data['pastesite'], paste_data['pasteid']))
@@ -237,6 +237,17 @@ def paste_scanner():
237237
# Else use the rule name
238238
else:
239239
results.append(match.rule)
240+
241+
# Store additional fields for passing on to post processing
242+
encoded_paste_data = raw_paste_data.encode('utf-8')
243+
md5 = hashlib.md5(encoded_paste_data).hexdigest()
244+
sha256 = hashlib.sha256(encoded_paste_data).hexdigest()
245+
paste_data['MD5'] = md5
246+
paste_data['SHA256'] = sha256
247+
paste_data['raw_paste'] = raw_paste_data
248+
paste_data['YaraRule'] = results
249+
# Set the size for all pastes - This will override any size set by the source
250+
paste_data['size'] = len(raw_paste_data)
240251

241252
# Store all OverRides other options.
242253
paste_site = paste_data['confname']
@@ -282,21 +293,6 @@ def paste_scanner():
282293
results.append('no_match')
283294

284295
if len(results) > 0:
285-
286-
encoded_paste_data = raw_paste_data.encode('utf-8')
287-
md5 = hashlib.md5(encoded_paste_data).hexdigest()
288-
sha256 = hashlib.sha256(encoded_paste_data).hexdigest()
289-
paste_data['MD5'] = md5
290-
paste_data['SHA256'] = sha256
291-
# It is possible a post module modified or set this field.
292-
if not paste_data.get('raw_paste'):
293-
paste_data['raw_paste'] = raw_paste_data
294-
paste_data['size'] = len(raw_paste_data)
295-
else:
296-
# Set size based on modified value
297-
paste_data['size'] = len(paste_data['raw_paste'])
298-
299-
paste_data['YaraRule'] = results
300296
for output in outputs:
301297
try:
302298
output.store_paste(paste_data)

postprocess/post_b64.py

+8-37
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import io
21
import re
32
import hashlib
3+
import importlib
44
import gzip
55
import logging
6-
import requests
76
from base64 import b64decode
87
# This gets the raw paste and the paste_data json object
98
from common import parse_config
@@ -45,6 +44,7 @@ def run(results, raw_paste_data, paste_object):
4544
paste_object["decompressed_stream"] = encoded
4645
except Exception as e:
4746
logger.error("Unable to decompress gzip stream")
47+
4848
if rule == 'b64_exe':
4949
try:
5050
raw_exe = b64decode(raw_paste_data)
@@ -55,47 +55,18 @@ def run(results, raw_paste_data, paste_object):
5555
# We are guessing that the sample has been submitted, and crafting a URL
5656
paste_object["VT"] = 'https://www.virustotal.com/#/file/{0}'.format(paste_object["exe_md5"])
5757

58-
# Cuckoo
59-
if conf["post_process"]["post_b64"]["cuckoo"]["enabled"]:
60-
logger.info("Submitting to Cuckoo")
61-
try:
62-
task_id = send_to_cuckoo(raw_exe, paste_object["pasteid"])
63-
paste_object["Cuckoo Task ID"] = task_id
64-
logger.info("exe submitted to Cuckoo with task id {0}".format(task_id))
65-
except Exception as e:
66-
logger.error("Unabled to submit sample to cuckoo")
67-
68-
# Viper
69-
if conf["post_process"]["post_b64"]["viper"]["enabled"]:
70-
send_to_cuckoo(raw_exe, paste_object["pasteid"])
71-
72-
# VirusTotal
58+
# If sandbox modules are enabled then submit the file
59+
for sandbox, sandbox_values in conf["sandboxes"].items():
60+
if sandbox_values["enabled"]:
61+
logger.info("Uploading file {0} using {1}".format(paste_object["pasteid"], sandbox_values["module"]))
62+
sandbox_module = importlib.import_module(sandbox_values["module"])
63+
paste_object = sandbox_module.upload_file(raw_exe, paste_object)
7364

7465
except Exception as e:
7566
logger.error("Unable to decode exe file")
7667

77-
7868
# Get unique domain count
7969
# Update the json
8070

8171
# Send the updated json back
8272
return paste_object
83-
84-
85-
def send_to_cuckoo(raw_exe, pasteid):
86-
cuckoo_ip = conf["post_process"]["post_b64"]["cuckoo"]["api_host"]
87-
cuckoo_port = conf["post_process"]["post_b64"]["cuckoo"]["api_port"]
88-
cuckoo_host = 'http://{0}:{1}'.format(cuckoo_ip, cuckoo_port)
89-
submit_file_url = '{0}/tasks/create/file'.format(cuckoo_host)
90-
files = {'file': ('{0}.exe'.format(pasteid), io.BytesIO(raw_exe))}
91-
submit_file = requests.post(submit_file_url, files=files).json()
92-
task_id = None
93-
try:
94-
task_id = submit_file['task_id']
95-
except KeyError:
96-
try:
97-
task_id = submit_file['task_ids'][0]
98-
except KeyError:
99-
logger.error(submit_file)
100-
101-
return task_id

sandboxes/__init__.py

Whitespace-only changes.

sandboxes/cuckoo.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import io
2+
import logging
3+
import requests
4+
from common import parse_config
5+
conf = parse_config()
6+
7+
logger = logging.getLogger('pastehunter')
8+
9+
def upload_file(raw_file, paste_object):
10+
try:
11+
task_id = send_to_cuckoo(raw_file, paste_object["pasteid"])
12+
paste_object["Cuckoo Task ID"] = task_id
13+
logger.info("exe submitted to Cuckoo with task id {0}".format(task_id))
14+
except Exception as e:
15+
logger.error("Unabled to submit sample to cuckoo")
16+
17+
# Send any updated json back
18+
return paste_object
19+
20+
def send_to_cuckoo(raw_exe, pasteid):
21+
cuckoo_ip = conf["sandboxes"]["cuckoo"]["api_host"]
22+
cuckoo_port = conf["sandboxes"]["cuckoo"]["api_port"]
23+
cuckoo_host = 'http://{0}:{1}'.format(cuckoo_ip, cuckoo_port)
24+
submit_file_url = '{0}/tasks/create/file'.format(cuckoo_host)
25+
files = {'file': ('{0}.exe'.format(pasteid), io.BytesIO(raw_exe))}
26+
submit_file = requests.post(submit_file_url, files=files).json()
27+
task_id = None
28+
try:
29+
task_id = submit_file['task_id']
30+
except KeyError:
31+
try:
32+
task_id = submit_file['task_ids'][0]
33+
except KeyError:
34+
logger.error(submit_file)
35+
36+
return task_id

sandboxes/viper.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import io
2+
import logging
3+
import requests
4+
from common import parse_config
5+
conf = parse_config()
6+
7+
logger = logging.getLogger('pastehunter')
8+
9+
def upload_file(raw_file, paste_object):
10+
viper_ip = conf["sandboxes"]["viper"]["api_host"]
11+
viper_port = conf["sandboxes"]["viper"]["api_port"]
12+
viper_host = 'http://{0}:{1}'.format(viper_ip, viper_port)
13+
14+
submit_file_url = '{0}/tasks/create/file'.format(viper_host)
15+
files = {'file': ('{0}.exe'.format(paste_object["pasteid"]), io.BytesIO(raw_file))}
16+
submit_file = requests.post(submit_file_url, files=files).json()
17+
18+
# Send any updated json back
19+
return paste_object

settings.json.sample

+17-12
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,22 @@
153153
"format": "%(asctime)s [%(threadName)-12.12s] %(levelname)s:%(message)s"
154154
},
155155
"general": {
156-
"run_frequency": 300
156+
"run_frequency": 300,
157+
"process_timeout": 5
158+
},
159+
"sandboxes": {
160+
"cuckoo": {
161+
"enabled": false,
162+
"module": "sandboxes.cuckoo",
163+
"api_host": "127.0.0.1",
164+
"api_port": 8080
165+
},
166+
"viper": {
167+
"enabled": false,
168+
"module": "sandboxes.viper",
169+
"api_host": "127.0.0.1",
170+
"api_port": 8080
171+
}
157172
},
158173
"post_process": {
159174
"post_email": {
@@ -164,17 +179,7 @@
164179
"post_b64": {
165180
"enabled": true,
166181
"module": "postprocess.post_b64",
167-
"rule_list": ["b64_exe", "b64_rar", "b64_zip", "b64_gzip"],
168-
"cuckoo": {
169-
"enabled": false,
170-
"api_host": "127.0.0.1",
171-
"api_port": 8080
172-
},
173-
"viper": {
174-
"enabled": false,
175-
"api_host": "127.0.0.1",
176-
"api_port": 8080
177-
}
182+
"rule_list": ["b64_exe", "b64_rar", "b64_zip", "b64_gzip"]
178183
},
179184
"post_entropy": {
180185
"enabled": false,

0 commit comments

Comments
 (0)