Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi shellcraft cmd #2398

Merged
merged 11 commits into from
Jun 23, 2024
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,18 @@ jobs:
pwn shellcraft --list |tail
pwn shellcraft -l --syscalls |tail
pwn shellcraft -l execve
pwn shellcraft -l execve + exit
pwn shellcraft --show i386.linux.loader_append
pwn shellcraft --show i386.linux.loader_append + i386.linux.sh
pwn shellcraft -f asm --color amd64.linux.sh
pwn shellcraft -f asm --color amd64.linux.setreuid + amd64.linux.cat /etc/passwd
pwn shellcraft -f asm --color amd64.linux.setreuid = amd64.linux.cat /key+secret --delim =
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f elf amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd + amd64.linux.sh </dev/null
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f c amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f str aarch64.linux.sh </dev/null
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2376][2376] Return buffered data on first EOF in tube.readline()
- [#2387][2387] Convert apport_corefile() output from bytes-like object to string
- [#2388][2388] libcdb: add `offline_only` to `search_by_symbol_offsets`
- [#2398][2398] Add support for generating multiple shellcodes at a time in shellcraft
- [#2415][2415] Add shellcraft template for IPv6 socket
- [#2405][2405] Add "none" ssh authentication method

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

Expand Down
192 changes: 112 additions & 80 deletions pwnlib/commandline/shellcraft.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,9 @@ def _string(s):

p.add_argument(
'shellcode',
nargs = '?',
help = 'The shellcode you want',
type = str
)

p.add_argument(
'args',
nargs = '*',
metavar = 'arg',
default = (),
help = 'Argument to the chosen shellcode',
help = 'The shellcodes you want. shellcode [args ...] [+ shellcode [args ...]]',
type = str
)

p.add_argument(
Expand All @@ -91,6 +83,12 @@ def _string(s):
action='store_true'
)

p.add_argument(
'--delim',
help='Set the delimiter between multilple shellcodes',
default='+'
)

p.add_argument(
'-b',
'--before',
Expand Down Expand Up @@ -172,24 +170,50 @@ def _string(s):
help='Generated ELF is a shared library'
)

def get_template(name):
func = shellcraft
for attr in name.split('.'):
func = getattr(func, attr)
return func
def get_template(shellcodes):
funcs = []
for shellcode in shellcodes:
func = shellcraft
cur_name = shellcode[0]
args = []
if len(shellcode) > 1:
args = shellcode[1:]
for attr in cur_name.split('.'):
func = getattr(func, attr)
funcs.append((cur_name, func, args))
return funcs

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

def main(args):
delim = '+'
if args.delim:
delim = args.delim.strip()

shellcodes = []
if args.shellcode:
current = []
for s in args.shellcode:
if s.strip() == delim:
shellcodes.append(current)
current = []
else:
current.append(s)
if len(current) > 0:
shellcodes.append(current)

if args.list:
templates = shellcraft.templates

if args.shellcode:
templates = filter(lambda a: args.shellcode in a, templates)
template_array = []
for s in shellcodes:
template_array.extend(list(filter(lambda a: s[0] in a, templates)))
templates = template_array
elif not args.syscalls:
templates = filter(is_not_a_syscall_template, templates)
templates = list(filter(is_not_a_syscall_template, templates))

print('\n'.join(templates))
exit()
Expand All @@ -199,84 +223,92 @@ def main(args):
exit()

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

if args.show:
# remove doctests
doc = []
in_doctest = False
block_indent = None
caption = None
lines = func.__doc__.splitlines()
i = 0
while i < len(lines):
line = lines[i]
if line.lstrip().startswith('>>>'):
# this line starts a doctest
in_doctest = True
block_indent = None
if caption:
# delete back up to the caption
doc = doc[:caption - i]
caption = None
elif line == '':
# skip blank lines
pass
elif in_doctest:
# indentation marks the end of a doctest
indent = len(line) - len(line.lstrip())
if block_indent is None:
if not line.lstrip().startswith('...'):
block_indent = indent
elif indent < block_indent:
in_doctest = False
for (name, func, _args) in funcs:
# remove doctests
doc = []
in_doctest = False
block_indent = None
caption = None
lines = func.__doc__.splitlines()
i = 0
if len(funcs) > 1:
print('%s:' % name)
while i < len(lines):
line = lines[i]
if line.lstrip().startswith('>>>'):
# this line starts a doctest
in_doctest = True
block_indent = None
# re-evalutate this line
continue
elif line.endswith(':'):
# save index of caption
caption = i
else:
# this is not blank space and we're not in a doctest, so the
# previous caption (if any) was not for a doctest
caption = None

if not in_doctest:
doc.append(line)
i += 1
print('\n'.join(doc).rstrip())
if caption:
# delete back up to the caption
doc = doc[:caption - i]
caption = None
elif line == '':
# skip blank lines
pass
elif in_doctest:
# indentation marks the end of a doctest
indent = len(line) - len(line.lstrip())
if block_indent is None:
if not line.lstrip().startswith('...'):
block_indent = indent
elif indent < block_indent:
in_doctest = False
block_indent = None
# re-evalutate this line
continue
elif line.endswith(':'):
# save index of caption
caption = i
else:
# this is not blank space and we're not in a doctest, so the
# previous caption (if any) was not for a doctest
caption = None

if not in_doctest:
doc.append(line)
i += 1
print('\n'.join(doc).rstrip())
if len(funcs) > 1:
print('')
exit()

defargs = len(six.get_function_defaults(func) or ())
reqargs = six.get_function_code(func).co_argcount - defargs
if len(args.args) < reqargs:
if defargs > 0:
log.critical('%s takes at least %d arguments' % (args.shellcode, reqargs))
sys.exit(1)
else:
log.critical('%s takes exactly %d arguments' % (args.shellcode, reqargs))
sys.exit(1)
code_array = []
for (name, func, func_args) in funcs:
defargs = len(six.get_function_defaults(func) or ())
reqargs = six.get_function_code(func).co_argcount - defargs

# Captain uglyness saves the day!
for i, val in enumerate(args.args):
try:
args.args[i] = util.safeeval.expr(val)
except ValueError:
pass
if len(func_args) < reqargs:
if defargs > 0:
log.critical('%s takes at least %d arguments' % (name, reqargs))
sys.exit(1)
else:
log.critical('%s takes exactly %d arguments' % (name, reqargs))
sys.exit(1)

# Captain uglyness saves the day!
for i, val in enumerate(func_args):
try:
func_args[i] = util.safeeval.expr(val)
except ValueError:
pass

# And he strikes again!
list(map(common.context_arg, args.shellcode.split('.')))
code = func(*args.args)
# And he strikes again!
list(map(common.context_arg, name.split('.')))
code_array.append(func(*func_args))

code = "".join(code_array)

if args.before:
code = shellcraft.trap() + code
if args.after:
code = code + shellcraft.trap()


if args.format in ['a', 'asm', 'assembly']:
if args.color:
from pygments import highlight
Expand Down Expand Up @@ -319,7 +351,7 @@ def main(args):
else:
args.format = 'raw'

arch = args.shellcode.split('.')[0]
arch = name.split('.')[0]

if args.debug:
if not args.avoid:
Expand Down