Skip to content

Commit

Permalink
Merge branch 'dev' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
peace-maker committed Jul 23, 2024
2 parents ac816c4 + 4284c3f commit 2248751
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 91 deletions.
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ 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
- [#2427][2427] Document behaviour of remote()'s sni argument as string.
- [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug()

[2360]: https://github.com/Gallopsled/pwntools/pull/2360
Expand All @@ -96,6 +100,10 @@ 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
[2427]: https://github.com/Gallopsled/pwntools/pull/2405
[2382]: https://github.com/Gallopsled/pwntools/pull/2382

## 4.13.0 (`beta`)
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
5 changes: 4 additions & 1 deletion pwnlib/shellcraft/templates/aarch64/linux/socket.asm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
Creates a new socket
</%docstring>
<%
sockaddr, length, address_family = sockaddr('127.0.0.1', 1, network)
if network == 'ipv4':
sockaddr, length, address_family = sockaddr('127.0.0.1', 1, network)
elif network == 'ipv6':
sockaddr, length, address_family = sockaddr('::1', 1, network)
socktype = {
'tcp': SOCK_STREAM,
'udp': SOCK_DGRAM
Expand Down
5 changes: 4 additions & 1 deletion pwnlib/shellcraft/templates/amd64/linux/socket.asm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
Creates a new socket
</%docstring>
<%
sockaddr, length, address_family = sockaddr('127.0.0.1', 1, network)
if network == 'ipv4':
sockaddr, length, address_family = sockaddr('127.0.0.1', 1, network)
elif network == 'ipv6':
sockaddr, length, address_family = sockaddr('::1', 1, network)
socktype = {
'tcp': SOCK_STREAM,
'udp': SOCK_DGRAM
Expand Down
8 changes: 4 additions & 4 deletions pwnlib/tubes/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class remote(sock):
fam: The string "any", "ipv4" or "ipv6" or an integer to pass to :func:`socket.getaddrinfo`.
typ: The string "tcp" or "udp" or an integer to pass to :func:`socket.getaddrinfo`.
timeout: A positive number, None or the string "default".
sock(:class:`socket.socket`): Socket to inherit, rather than connecting
ssl(bool): Wrap the socket with SSL
ssl_context(ssl.SSLContext): Specify SSLContext used to wrap the socket.
sni: Set 'server_hostname' in ssl_args based on the host parameter.
sock(socket.socket): Socket to inherit, rather than connecting
ssl_args(dict): Pass ssl.wrap_socket named arguments in a dictionary.
ssl_args(dict): Pass :func:`ssl.wrap_socket` named arguments in a dictionary.
sni(str,bool): Set 'server_hostname' in ssl_args. Set to True to set it based on the host argument. Set to False to not provide any value. Default is True.
Examples:
Expand Down Expand Up @@ -57,7 +57,7 @@ class remote(sock):

def __init__(self, host, port,
fam = "any", typ = "tcp",
ssl=False, sock=None, ssl_context=None, ssl_args=None, sni=True,
sock=None, ssl=False, ssl_context=None, ssl_args=None, sni=True,
*args, **kwargs):
super(remote, self).__init__(*args, **kwargs)

Expand Down
17 changes: 12 additions & 5 deletions pwnlib/tubes/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,8 @@ class ssh(Timeout, Logger):

def __init__(self, user=None, host=None, port=22, password=None, key=None,
keyfile=None, proxy_command=None, proxy_sock=None, level=None,
cache=True, ssh_agent=False, ignore_config=False, raw=False, *a, **kw):
cache=True, ssh_agent=False, ignore_config=False, raw=False,
auth_none=False, *a, **kw):
"""Creates a new ssh connection.
Arguments:
Expand All @@ -587,10 +588,11 @@ def __init__(self, user=None, host=None, port=22, password=None, key=None,
proxy_sock(str): Use this socket instead of connecting to the host.
timeout: Timeout, in seconds
level: Log level
cache: Cache downloaded files (by hash/size/timestamp)
ssh_agent: If :const:`True`, enable usage of keys via ssh-agent
ignore_config: If :const:`True`, disable usage of ~/.ssh/config and ~/.ssh/authorized_keys
raw: If :const:`True`, assume a non-standard shell and don't probe the environment
cache(bool): Cache downloaded files (by hash/size/timestamp)
ssh_agent(bool): If :const:`True`, enable usage of keys via ssh-agent
ignore_config(bool): If :const:`True`, disable usage of ~/.ssh/config and ~/.ssh/authorized_keys
raw(bool): If :const:`True`, assume a non-standard shell and don't probe the environment
auth_none(bool): If :const:`True`, try to authenticate with no authentication methods
NOTE: The proxy_command and proxy_sock arguments is only available if a
fairly new version of paramiko is used.
Expand Down Expand Up @@ -686,6 +688,11 @@ def __init__(self, user=None, host=None, port=22, password=None, key=None,
" To remove the existing entry from your known_hosts and trust the new key, run the following commands:\n"
" $ ssh-keygen -R %(host)s\n"
" $ ssh-keygen -R [%(host)s]:%(port)s" % locals())
except paramiko.SSHException as e:
if user and auth_none and str(e) == "No authentication methods available":
self.client.get_transport().auth_none(user)
else:
raise

self.transport = self.client.get_transport()
self.transport.use_compression(True)
Expand Down
8 changes: 8 additions & 0 deletions pwnlib/util/fiddling.py
Original file line number Diff line number Diff line change
Expand Up @@ -1097,3 +1097,11 @@ def js_unescape(s, **kwargs):
p += 1

return b''.join(res)

def tty_escape(s, lnext=b'\x16', dangerous=bytes(bytearray(range(0x20)))):
s = s.replace(lnext, lnext * 2)
for b in bytearray(dangerous):
if b in lnext: continue
b = bytearray([b])
s = s.replace(b, lnext + b)
return s

0 comments on commit 2248751

Please sign in to comment.