Skip to content

Commit 736cf9d

Browse files
cocoa-xuArusekk
andauthored
feat: multi shellcraft cmd (#2398)
* added support for multiple shellcraft commands * updated CHANGELOG.md * fix `pwn shellcraft -l shellcode` * fix `get_template` * strip strings after spliting by the delimiter * fix `pwn shellcraft --show shellcode` * added some tests in ci * better handling for splitting by delimiter --------- Co-authored-by: Arusekk <[email protected]>
1 parent 4dc8d60 commit 736cf9d

File tree

3 files changed

+121
-80
lines changed

3 files changed

+121
-80
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,18 @@ jobs:
150150
pwn shellcraft --list |tail
151151
pwn shellcraft -l --syscalls |tail
152152
pwn shellcraft -l execve
153+
pwn shellcraft -l execve + exit
153154
pwn shellcraft --show i386.linux.loader_append
155+
pwn shellcraft --show i386.linux.loader_append + i386.linux.sh
154156
pwn shellcraft -f asm --color amd64.linux.sh
157+
pwn shellcraft -f asm --color amd64.linux.setreuid + amd64.linux.cat /etc/passwd
158+
pwn shellcraft -f asm --color amd64.linux.setreuid = amd64.linux.cat /key+secret --delim =
155159
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
160+
pwn shellcraft -f elf amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null |pwn hex
156161
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
162+
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd + amd64.linux.sh </dev/null
157163
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
164+
pwn shellcraft -f c amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null
158165
pwn shellcraft -f str aarch64.linux.sh </dev/null
159166
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
160167
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ The table below shows which release corresponds to each branch, and what date th
8383
- [#2376][2376] Return buffered data on first EOF in tube.readline()
8484
- [#2387][2387] Convert apport_corefile() output from bytes-like object to string
8585
- [#2388][2388] libcdb: add `offline_only` to `search_by_symbol_offsets`
86+
- [#2398][2398] Add support for generating multiple shellcodes at a time in shellcraft
8687
- [#2415][2415] Add shellcraft template for IPv6 socket
8788
- [#2405][2405] Add "none" ssh authentication method
8889

@@ -97,6 +98,7 @@ The table below shows which release corresponds to each branch, and what date th
9798
[2376]: https://github.com/Gallopsled/pwntools/pull/2376
9899
[2387]: https://github.com/Gallopsled/pwntools/pull/2387
99100
[2388]: https://github.com/Gallopsled/pwntools/pull/2388
101+
[2398]: https://github.com/Gallopsled/pwntools/pull/2398
100102
[2415]: https://github.com/Gallopsled/pwntools/pull/2415
101103
[2405]: https://github.com/Gallopsled/pwntools/pull/2405
102104

pwnlib/commandline/shellcraft.py

Lines changed: 112 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,9 @@ def _string(s):
7171

7272
p.add_argument(
7373
'shellcode',
74-
nargs = '?',
75-
help = 'The shellcode you want',
76-
type = str
77-
)
78-
79-
p.add_argument(
80-
'args',
8174
nargs = '*',
82-
metavar = 'arg',
83-
default = (),
84-
help = 'Argument to the chosen shellcode',
75+
help = 'The shellcodes you want. shellcode [args ...] [+ shellcode [args ...]]',
76+
type = str
8577
)
8678

8779
p.add_argument(
@@ -91,6 +83,12 @@ def _string(s):
9183
action='store_true'
9284
)
9385

86+
p.add_argument(
87+
'--delim',
88+
help='Set the delimiter between multilple shellcodes',
89+
default='+'
90+
)
91+
9492
p.add_argument(
9593
'-b',
9694
'--before',
@@ -172,24 +170,50 @@ def _string(s):
172170
help='Generated ELF is a shared library'
173171
)
174172

175-
def get_template(name):
176-
func = shellcraft
177-
for attr in name.split('.'):
178-
func = getattr(func, attr)
179-
return func
173+
def get_template(shellcodes):
174+
funcs = []
175+
for shellcode in shellcodes:
176+
func = shellcraft
177+
cur_name = shellcode[0]
178+
args = []
179+
if len(shellcode) > 1:
180+
args = shellcode[1:]
181+
for attr in cur_name.split('.'):
182+
func = getattr(func, attr)
183+
funcs.append((cur_name, func, args))
184+
return funcs
180185

181186
def is_not_a_syscall_template(name):
182187
template_src = shellcraft._get_source(name)
183188
return '/syscalls' not in template_src
184189

185190
def main(args):
191+
delim = '+'
192+
if args.delim:
193+
delim = args.delim.strip()
194+
195+
shellcodes = []
196+
if args.shellcode:
197+
current = []
198+
for s in args.shellcode:
199+
if s.strip() == delim:
200+
shellcodes.append(current)
201+
current = []
202+
else:
203+
current.append(s)
204+
if len(current) > 0:
205+
shellcodes.append(current)
206+
186207
if args.list:
187208
templates = shellcraft.templates
188209

189210
if args.shellcode:
190-
templates = filter(lambda a: args.shellcode in a, templates)
211+
template_array = []
212+
for s in shellcodes:
213+
template_array.extend(list(filter(lambda a: s[0] in a, templates)))
214+
templates = template_array
191215
elif not args.syscalls:
192-
templates = filter(is_not_a_syscall_template, templates)
216+
templates = list(filter(is_not_a_syscall_template, templates))
193217

194218
print('\n'.join(templates))
195219
exit()
@@ -199,84 +223,92 @@ def main(args):
199223
exit()
200224

201225
try:
202-
func = get_template(args.shellcode)
226+
funcs = get_template(shellcodes)
203227
except AttributeError:
204228
log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
205229

206230
if args.show:
207-
# remove doctests
208-
doc = []
209-
in_doctest = False
210-
block_indent = None
211-
caption = None
212-
lines = func.__doc__.splitlines()
213-
i = 0
214-
while i < len(lines):
215-
line = lines[i]
216-
if line.lstrip().startswith('>>>'):
217-
# this line starts a doctest
218-
in_doctest = True
219-
block_indent = None
220-
if caption:
221-
# delete back up to the caption
222-
doc = doc[:caption - i]
223-
caption = None
224-
elif line == '':
225-
# skip blank lines
226-
pass
227-
elif in_doctest:
228-
# indentation marks the end of a doctest
229-
indent = len(line) - len(line.lstrip())
230-
if block_indent is None:
231-
if not line.lstrip().startswith('...'):
232-
block_indent = indent
233-
elif indent < block_indent:
234-
in_doctest = False
231+
for (name, func, _args) in funcs:
232+
# remove doctests
233+
doc = []
234+
in_doctest = False
235+
block_indent = None
236+
caption = None
237+
lines = func.__doc__.splitlines()
238+
i = 0
239+
if len(funcs) > 1:
240+
print('%s:' % name)
241+
while i < len(lines):
242+
line = lines[i]
243+
if line.lstrip().startswith('>>>'):
244+
# this line starts a doctest
245+
in_doctest = True
235246
block_indent = None
236-
# re-evalutate this line
237-
continue
238-
elif line.endswith(':'):
239-
# save index of caption
240-
caption = i
241-
else:
242-
# this is not blank space and we're not in a doctest, so the
243-
# previous caption (if any) was not for a doctest
244-
caption = None
245-
246-
if not in_doctest:
247-
doc.append(line)
248-
i += 1
249-
print('\n'.join(doc).rstrip())
247+
if caption:
248+
# delete back up to the caption
249+
doc = doc[:caption - i]
250+
caption = None
251+
elif line == '':
252+
# skip blank lines
253+
pass
254+
elif in_doctest:
255+
# indentation marks the end of a doctest
256+
indent = len(line) - len(line.lstrip())
257+
if block_indent is None:
258+
if not line.lstrip().startswith('...'):
259+
block_indent = indent
260+
elif indent < block_indent:
261+
in_doctest = False
262+
block_indent = None
263+
# re-evalutate this line
264+
continue
265+
elif line.endswith(':'):
266+
# save index of caption
267+
caption = i
268+
else:
269+
# this is not blank space and we're not in a doctest, so the
270+
# previous caption (if any) was not for a doctest
271+
caption = None
272+
273+
if not in_doctest:
274+
doc.append(line)
275+
i += 1
276+
print('\n'.join(doc).rstrip())
277+
if len(funcs) > 1:
278+
print('')
250279
exit()
251280

252-
defargs = len(six.get_function_defaults(func) or ())
253-
reqargs = six.get_function_code(func).co_argcount - defargs
254-
if len(args.args) < reqargs:
255-
if defargs > 0:
256-
log.critical('%s takes at least %d arguments' % (args.shellcode, reqargs))
257-
sys.exit(1)
258-
else:
259-
log.critical('%s takes exactly %d arguments' % (args.shellcode, reqargs))
260-
sys.exit(1)
281+
code_array = []
282+
for (name, func, func_args) in funcs:
283+
defargs = len(six.get_function_defaults(func) or ())
284+
reqargs = six.get_function_code(func).co_argcount - defargs
261285

262-
# Captain uglyness saves the day!
263-
for i, val in enumerate(args.args):
264-
try:
265-
args.args[i] = util.safeeval.expr(val)
266-
except ValueError:
267-
pass
286+
if len(func_args) < reqargs:
287+
if defargs > 0:
288+
log.critical('%s takes at least %d arguments' % (name, reqargs))
289+
sys.exit(1)
290+
else:
291+
log.critical('%s takes exactly %d arguments' % (name, reqargs))
292+
sys.exit(1)
293+
294+
# Captain uglyness saves the day!
295+
for i, val in enumerate(func_args):
296+
try:
297+
func_args[i] = util.safeeval.expr(val)
298+
except ValueError:
299+
pass
268300

269-
# And he strikes again!
270-
list(map(common.context_arg, args.shellcode.split('.')))
271-
code = func(*args.args)
301+
# And he strikes again!
302+
list(map(common.context_arg, name.split('.')))
303+
code_array.append(func(*func_args))
272304

305+
code = "".join(code_array)
273306

274307
if args.before:
275308
code = shellcraft.trap() + code
276309
if args.after:
277310
code = code + shellcraft.trap()
278311

279-
280312
if args.format in ['a', 'asm', 'assembly']:
281313
if args.color:
282314
from pygments import highlight
@@ -319,7 +351,7 @@ def main(args):
319351
else:
320352
args.format = 'raw'
321353

322-
arch = args.shellcode.split('.')[0]
354+
arch = name.split('.')[0]
323355

324356
if args.debug:
325357
if not args.avoid:

0 commit comments

Comments
 (0)