Skip to content

Commit 3ea58d7

Browse files
committed
Add tests and fix corner cases
1 parent cc6aead commit 3ea58d7

File tree

1 file changed

+45
-10
lines changed

1 file changed

+45
-10
lines changed

pwnlib/tubes/tube.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,12 +1091,14 @@ def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_
10911091
```
10921092
loop:
10931093
echo <chunk> | base64 -d >> <target_path>.<compression>
1094-
<compression> -d <target_path>.<compression>
1094+
<compression> -d -f <target_path>.<compression>
10951095
chmod <chmod_flags> <target_path>
10961096
```
10971097
10981098
It is assumed that a `base64` command is available on the target system.
1099-
When ``compression`` is ``auto`` the best compression utility available is chosen.
1099+
When ``compression`` is ``auto`` the best compression utility available
1100+
between ``gzip`` and ``xz`` is chosen with a fallback to uncompressed
1101+
upload.
11001102
11011103
Arguments:
11021104
@@ -1108,6 +1110,28 @@ def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_
11081110
compression(str): The compression to use. ``auto`` to automatically choose the best compression or ``gzip`` or ``xz``.
11091111
end_marker(str): The marker to use to detect the end of the output. Only used when prompt is not set.
11101112
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()
11111135
"""
11121136
echo_end = ""
11131137
if not prompt:
@@ -1125,7 +1149,7 @@ def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_
11251149
self.sendline("echo {}".format(end_marker).encode())
11261150
if compression == 'auto':
11271151
for utility in possible_compression:
1128-
self.sendlineafter(end_markerb, "command -v {} && echo YEP || echo NOPE;{}".format(utility, echo_end).encode())
1152+
self.sendlineafter(end_markerb, "command -v {} && echo YEP || echo NOPE{}".format(utility, echo_end).encode())
11291153
result = self.recvuntil([b'YEP', b'NOPE'])
11301154
if b'YEP' in result:
11311155
compression_mode = utility
@@ -1137,33 +1161,44 @@ def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_
11371161

11381162
self.debug('Manually uploading using compression mode: %s', compression_mode)
11391163

1164+
compressed_data = b''
11401165
if compression_mode == 'xz':
11411166
import lzma
1142-
data = lzma.compress(data, format=lzma.FORMAT_XZ, preset=9)
1167+
compressed_data = lzma.compress(data, format=lzma.FORMAT_XZ, preset=9)
11431168
compressed_path = target_path + '.xz'
11441169
elif compression_mode == 'gzip':
11451170
import gzip
1146-
data = gzip.compress(data, compresslevel=9)
1171+
compressed_data = gzip.compress(data, compresslevel=9)
11471172
compressed_path = target_path + '.gz'
11481173
else:
11491174
compressed_path = target_path
1175+
1176+
# Don't compress if it doesn't reduce the size.
1177+
if len(compressed_data) >= len(data):
1178+
compression_mode = None
1179+
compressed_path = target_path
1180+
else:
1181+
data = compressed_data
11501182

11511183
# Upload data in `chunk_size` chunks. Assume base64 is available.
11521184
with self.progress('Uploading payload') as p:
11531185
for idx, chunk in enumerate(iters.group(chunk_size, data)):
1186+
if None in chunk:
1187+
chunk = chunk[:chunk.index(None)]
11541188
if idx == 0:
1155-
self.sendlineafter(end_markerb, "echo {} | base64 -d > {}{}".format(fiddling.b64e(chunk), compressed_path, echo_end).encode())
1189+
self.sendlineafter(end_markerb, "echo {} | base64 -d > {}{}".format(fiddling.b64e(bytes(chunk)), compressed_path, echo_end).encode())
11561190
else:
1157-
self.sendlineafter(end_markerb, "echo {} | base64 -d >> {}{}".format(fiddling.b64e(chunk), compressed_path, echo_end).encode())
1158-
p.status('{}/{}'.format(idx, len(data)//chunk_size))
1191+
self.sendlineafter(end_markerb, "echo {} | base64 -d >> {}{}".format(fiddling.b64e(bytes(chunk)), compressed_path, echo_end).encode())
1192+
p.status('{}/{} {}'.format(idx+1, len(data)//chunk_size+1, misc.size(idx*chunk_size + len(chunk))))
1193+
p.success(misc.size(len(data)))
11591194

11601195
# Decompress the file and set the permissions.
11611196
if compression_mode is not None:
1162-
self.sendlineafter(end_markerb, '{} -d {}{}'.format(compression_mode, compressed_path, echo_end).encode())
1197+
self.sendlineafter(end_markerb, '{} -d -f {}{}'.format(compression_mode, compressed_path, echo_end).encode())
11631198
if chmod_flags:
11641199
self.sendlineafter(end_markerb, 'chmod {} {}{}'.format(chmod_flags, target_path, echo_end).encode())
11651200
if not prompt:
1166-
self.recvuntil(end_markerb)
1201+
self.recvuntil(end_markerb + b'\n')
11671202

11681203
def connect_input(self, other):
11691204
"""connect_input(other)

0 commit comments

Comments
 (0)