|
21 | 21 | from pwnlib.log import Logger
|
22 | 22 | from pwnlib.timeout import Timeout
|
23 | 23 | from pwnlib.tubes.buffer import Buffer
|
| 24 | +from pwnlib.util import fiddling |
| 25 | +from pwnlib.util import iters |
24 | 26 | from pwnlib.util import misc
|
25 | 27 | from pwnlib.util import packing
|
26 | 28 |
|
@@ -1077,6 +1079,131 @@ def clean_and_log(self, timeout = 0.05):
|
1077 | 1079 | with context.local(log_level='debug'):
|
1078 | 1080 | return cached_data + self.clean(timeout)
|
1079 | 1081 |
|
| 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 | + |
1080 | 1207 | def connect_input(self, other):
|
1081 | 1208 | """connect_input(other)
|
1082 | 1209 |
|
|
0 commit comments