Skip to content

Commit aa35829

Browse files
authored
Merge branch 'dev' into add_ko_file_search_support
2 parents 11a9208 + 6f0793e commit aa35829

File tree

5 files changed

+170
-8
lines changed

5 files changed

+170
-8
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ The table below shows which release corresponds to each branch, and what date th
8484
- [#2482][2482] Throw error when using `sni` and setting `server_hostname` manually in `remote`
8585
- [#2478][2478] libcdb-cli: add `--offline-only`, refactor unstrip and add fetch parser for download libc-database
8686
- [#2484][2484] Allow to disable caching
87+
- [#2291][2291] Fix attaching to a gdbserver with tuple `gdb.attach(('0.0.0.0',12345))`
88+
- [#2410][2410] Add `tube.upload_manually` to upload files in chunks
8789
- [#2496][2496] Add linux ko file search support
8890

8991
[2471]: https://github.com/Gallopsled/pwntools/pull/2471
@@ -97,6 +99,8 @@ The table below shows which release corresponds to each branch, and what date th
9799
[2482]: https://github.com/Gallopsled/pwntools/pull/2482
98100
[2478]: https://github.com/Gallopsled/pwntools/pull/2478
99101
[2484]: https://github.com/Gallopsled/pwntools/pull/2484
102+
[2291]: https://github.com/Gallopsled/pwntools/pull/2291
103+
[2410]: https://github.com/Gallopsled/pwntools/pull/2410
100104
[2496]: https://github.com/Gallopsled/pwntools/pull/2496
101105

102106
## 4.14.0 (`beta`)

pwnlib/gdb.py

+26
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,8 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
950950
Process name. The youngest process is selected.
951951
:obj:`tuple`
952952
Host, port pair of a listening ``gdbserver``
953+
Tries to look up the target exe from the ``gdbserver`` commandline,
954+
requires explicit ``exe`` argument if the target exe is not in the commandline.
953955
:class:`.process`
954956
Process to connect to
955957
:class:`.sock`
@@ -1034,6 +1036,30 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
10341036
>>> io.sendline(b'echo Hello from bash && exit')
10351037
>>> io.recvall()
10361038
b'Hello from bash\n'
1039+
>>> server.close()
1040+
1041+
Attach to a gdbserver / gdbstub running on the local machine
1042+
by specifying the host and port tuple it is listening on.
1043+
(gdbserver always listens on 0.0.0.0)
1044+
1045+
>>> gdbserver = process(['gdbserver', '1.2.3.4:12345', '/bin/bash'])
1046+
>>> gdbserver.recvline_contains(b'Listening on port', timeout=10)
1047+
b'Listening on port 12345'
1048+
>>> pid = gdb.attach(('0.0.0.0', 12345), gdbscript='''
1049+
... tbreak main
1050+
... commands
1051+
... call puts("Hello from gdbserver debugger!")
1052+
... continue
1053+
... end
1054+
... ''')
1055+
>>> gdbserver.recvline(timeout=10) # doctest: +ELLIPSIS
1056+
b'Remote debugging from host 127.0.0.1, ...\n'
1057+
>>> gdbserver.recvline(timeout=10)
1058+
b'Hello from gdbserver debugger!\n'
1059+
>>> gdbserver.sendline(b'echo Hello from bash && exit')
1060+
>>> gdbserver.recvline(timeout=10)
1061+
b'Hello from bash\n'
1062+
>>> gdbserver.close()
10371063
10381064
Attach to processes running on a remote machine via an SSH :class:`.ssh` process
10391065

pwnlib/tubes/tube.py

+127
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from pwnlib.log import Logger
2222
from pwnlib.timeout import Timeout
2323
from pwnlib.tubes.buffer import Buffer
24+
from pwnlib.util import fiddling
25+
from pwnlib.util import iters
2426
from pwnlib.util import misc
2527
from pwnlib.util import packing
2628

@@ -1077,6 +1079,131 @@ def clean_and_log(self, timeout = 0.05):
10771079
with context.local(log_level='debug'):
10781080
return cached_data + self.clean(timeout)
10791081

1082+
def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_size = 0x200, chmod_flags = 'u+x', compression='auto', end_marker = 'PWNTOOLS_DONE'):
1083+
"""upload_manually(data, target_path = './payload', prompt = b'$', chunk_size = 0x200, chmod_flags = 'u+x', compression='auto', end_marker = 'PWNTOOLS_DONE')
1084+
1085+
Upload a file manually using base64 encoding and compression.
1086+
This can be used when the tube is connected to a shell.
1087+
1088+
The file is uploaded in base64-encoded chunks by appending to a file
1089+
and then decompressing it:
1090+
1091+
```
1092+
loop:
1093+
echo <chunk> | base64 -d >> <target_path>.<compression>
1094+
<compression> -d -f <target_path>.<compression>
1095+
chmod <chmod_flags> <target_path>
1096+
```
1097+
1098+
It is assumed that a `base64` command is available on the target system.
1099+
When ``compression`` is ``auto`` the best compression utility available
1100+
between ``gzip`` and ``xz`` is chosen with a fallback to uncompressed
1101+
upload.
1102+
1103+
Arguments:
1104+
1105+
data(bytes): The data to upload.
1106+
target_path(str): The path to upload the data to.
1107+
prompt(bytes): The shell prompt to wait for.
1108+
chunk_size(int): The size of each chunk to upload.
1109+
chmod_flags(str): The flags to use with chmod. ``""`` to ignore.
1110+
compression(str): The compression to use. ``auto`` to automatically choose the best compression or ``gzip`` or ``xz``.
1111+
end_marker(str): The marker to use to detect the end of the output. Only used when prompt is not set.
1112+
1113+
Examples:
1114+
1115+
>>> l = listen()
1116+
>>> l.spawn_process('/bin/sh')
1117+
>>> r = remote('127.0.0.1', l.lport)
1118+
>>> r.upload_manually(b'some\\xca\\xfedata\\n', prompt=b'', chmod_flags='')
1119+
>>> r.sendline(b'cat ./payload')
1120+
>>> r.recvline()
1121+
b'some\\xca\\xfedata\\n'
1122+
1123+
>>> r.upload_manually(cyclic(0x1000), target_path='./cyclic_pattern', prompt=b'', chunk_size=0x10, compression='gzip')
1124+
>>> r.sendline(b'sha256sum ./cyclic_pattern')
1125+
>>> r.recvlineS(keepends=False).startswith(sha256sumhex(cyclic(0x1000)))
1126+
True
1127+
1128+
>>> blob = ELF.from_assembly(shellcraft.echo('Hello world!\\n') + shellcraft.exit(0))
1129+
>>> r.upload_manually(blob.data, prompt=b'')
1130+
>>> r.sendline(b'./payload')
1131+
>>> r.recvline()
1132+
b'Hello world!\\n'
1133+
>>> r.close()
1134+
>>> l.close()
1135+
"""
1136+
echo_end = ""
1137+
if not prompt:
1138+
echo_end = "; echo {}".format(end_marker)
1139+
end_markerb = end_marker.encode()
1140+
else:
1141+
end_markerb = prompt
1142+
1143+
# Detect available compression utility, fallback to uncompressed upload.
1144+
compression_mode = None
1145+
possible_compression = ['gzip']
1146+
if six.PY3:
1147+
possible_compression.insert(0, 'xz')
1148+
if not prompt:
1149+
self.sendline("echo {}".format(end_marker).encode())
1150+
if compression == 'auto':
1151+
for utility in possible_compression:
1152+
self.sendlineafter(end_markerb, "command -v {} && echo YEP || echo NOPE{}".format(utility, echo_end).encode())
1153+
result = self.recvuntil([b'YEP', b'NOPE'])
1154+
if b'YEP' in result:
1155+
compression_mode = utility
1156+
break
1157+
elif compression in possible_compression:
1158+
compression_mode = compression
1159+
else:
1160+
self.error('Invalid compression mode: %s, has to be one of %s', compression, possible_compression)
1161+
1162+
self.debug('Manually uploading using compression mode: %s', compression_mode)
1163+
1164+
compressed_data = b''
1165+
if compression_mode == 'xz':
1166+
import lzma
1167+
compressed_data = lzma.compress(data, format=lzma.FORMAT_XZ, preset=9)
1168+
compressed_path = target_path + '.xz'
1169+
elif compression_mode == 'gzip':
1170+
import gzip
1171+
from six import BytesIO
1172+
f = BytesIO()
1173+
with gzip.GzipFile(fileobj=f, mode='wb', compresslevel=9) as g:
1174+
g.write(data)
1175+
compressed_data = f.getvalue()
1176+
compressed_path = target_path + '.gz'
1177+
else:
1178+
compressed_path = target_path
1179+
1180+
# Don't compress if it doesn't reduce the size.
1181+
if len(compressed_data) >= len(data):
1182+
compression_mode = None
1183+
compressed_path = target_path
1184+
else:
1185+
data = compressed_data
1186+
1187+
# Upload data in `chunk_size` chunks. Assume base64 is available.
1188+
with self.progress('Uploading payload') as p:
1189+
for idx, chunk in enumerate(iters.group(chunk_size, data)):
1190+
if None in chunk:
1191+
chunk = chunk[:chunk.index(None)]
1192+
if idx == 0:
1193+
self.sendlineafter(end_markerb, "echo {} | base64 -d > {}{}".format(fiddling.b64e(bytearray(chunk)), compressed_path, echo_end).encode())
1194+
else:
1195+
self.sendlineafter(end_markerb, "echo {} | base64 -d >> {}{}".format(fiddling.b64e(bytearray(chunk)), compressed_path, echo_end).encode())
1196+
p.status('{}/{} {}'.format(idx+1, len(data)//chunk_size+1, misc.size(idx*chunk_size + len(chunk))))
1197+
p.success(misc.size(len(data)))
1198+
1199+
# Decompress the file and set the permissions.
1200+
if compression_mode is not None:
1201+
self.sendlineafter(end_markerb, '{} -d -f {}{}'.format(compression_mode, compressed_path, echo_end).encode())
1202+
if chmod_flags:
1203+
self.sendlineafter(end_markerb, 'chmod {} {}{}'.format(chmod_flags, target_path, echo_end).encode())
1204+
if not prompt:
1205+
self.recvuntil(end_markerb + b'\n')
1206+
10801207
def connect_input(self, other):
10811208
"""connect_input(other)
10821209

pwnlib/util/net.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -259,17 +259,17 @@ def sockinfos(addr, f, t):
259259
infos |= set(socket.getaddrinfo(sockaddr[0], sockaddr[1], socket.AF_INET6, t, proto, socket.AI_V4MAPPED))
260260
return infos
261261

262-
if local is not None:
263-
local = sockinfos(local, fam, typ)
264-
remote = sockinfos(remote, fam, typ)
262+
local = sockinfos(local, fam, typ)
263+
if remote is not None:
264+
remote = sockinfos(remote, fam, typ)
265265

266266
def match(c):
267267
laddrs = sockinfos(c.laddr, c.family, c.type)
268268
raddrs = sockinfos(c.raddr, c.family, c.type)
269-
if not (raddrs & remote):
269+
if not (laddrs & local):
270270
return False
271-
if local is None:
271+
if remote is None:
272272
return True
273-
return bool(laddrs & local)
273+
return bool(raddrs & remote)
274274

275275
return match

pwnlib/util/proc.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def pidof(target):
2727
- :class:`pwnlib.tubes.sock.sock`: singleton list of the PID at the
2828
remote end of `target` if it is running on the host. Otherwise an
2929
empty list.
30+
- :class:`pwnlib.tubes.ssh.ssh_channel`: singleton list of the PID of
31+
`target` on the remote system.
32+
- :class:`tuple`: singleton list of the PID at the local end of the
33+
connection to `target` if it is running on the host. Otherwise an
34+
empty list.
3035
3136
Arguments:
3237
target(object): The target whose PID(s) to find.
@@ -38,7 +43,7 @@ def pidof(target):
3843
3944
>>> l = tubes.listen.listen()
4045
>>> p = process(['curl', '-s', 'http://127.0.0.1:%d'%l.lport])
41-
>>> pidof(p) == pidof(l) == pidof(('127.0.0.1', l.lport))
46+
>>> pidof(p) == pidof(l) == pidof(('127.0.0.1', l.rport))
4247
True
4348
"""
4449
if isinstance(target, tubes.ssh.ssh_channel):
@@ -51,7 +56,7 @@ def pidof(target):
5156
return [c.pid for c in psutil.net_connections() if match(c)]
5257

5358
elif isinstance(target, tuple):
54-
match = sock_match(None, target)
59+
match = sock_match(target, None)
5560
return [c.pid for c in psutil.net_connections() if match(c)]
5661

5762
elif isinstance(target, tubes.process.process):

0 commit comments

Comments
 (0)