|
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,92 @@ 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 <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 is chosen. |
| 1100 | +
|
| 1101 | + Arguments: |
| 1102 | +
|
| 1103 | + data(bytes): The data to upload. |
| 1104 | + target_path(str): The path to upload the data to. |
| 1105 | + prompt(bytes): The shell prompt to wait for. |
| 1106 | + chunk_size(int): The size of each chunk to upload. |
| 1107 | + chmod_flags(str): The flags to use with chmod. ``""`` to ignore. |
| 1108 | + compression(str): The compression to use. ``auto`` to automatically choose the best compression or ``gzip`` or ``xz``. |
| 1109 | + end_marker(str): The marker to use to detect the end of the output. Only used when prompt is not set. |
| 1110 | +
|
| 1111 | + """ |
| 1112 | + echo_end = "" |
| 1113 | + if not prompt: |
| 1114 | + echo_end = "; echo {}".format(end_marker) |
| 1115 | + end_markerb = end_marker.encode() |
| 1116 | + else: |
| 1117 | + end_markerb = prompt |
| 1118 | + |
| 1119 | + # Detect available compression utility, fallback to uncompressed upload. |
| 1120 | + compression_mode = None |
| 1121 | + possible_compression = ['gzip'] |
| 1122 | + if six.PY3: |
| 1123 | + possible_compression.insert(0, 'xz') |
| 1124 | + if not prompt: |
| 1125 | + self.sendline("echo {}".format(end_marker).encode()) |
| 1126 | + if compression == 'auto': |
| 1127 | + for utility in possible_compression: |
| 1128 | + self.sendlineafter(end_markerb, "command -v {} && echo YEP || echo NOPE;{}".format(utility, echo_end).encode()) |
| 1129 | + result = self.recvuntil([b'YEP', b'NOPE']) |
| 1130 | + if b'YEP' in result: |
| 1131 | + compression_mode = utility |
| 1132 | + break |
| 1133 | + elif compression in possible_compression: |
| 1134 | + compression_mode = compression |
| 1135 | + else: |
| 1136 | + self.error('Invalid compression mode: %s, has to be one of %s', compression, possible_compression) |
| 1137 | + |
| 1138 | + self.debug('Manually uploading using compression mode: %s', compression_mode) |
| 1139 | + |
| 1140 | + if compression_mode == 'xz': |
| 1141 | + import lzma |
| 1142 | + data = lzma.compress(data, format=lzma.FORMAT_XZ, preset=9) |
| 1143 | + compressed_path = target_path + '.xz' |
| 1144 | + elif compression_mode == 'gzip': |
| 1145 | + import gzip |
| 1146 | + data = gzip.compress(data, compresslevel=9) |
| 1147 | + compressed_path = target_path + '.gz' |
| 1148 | + else: |
| 1149 | + compressed_path = target_path |
| 1150 | + |
| 1151 | + # Upload data in `chunk_size` chunks. Assume base64 is available. |
| 1152 | + with self.progress('Uploading payload') as p: |
| 1153 | + for idx, chunk in enumerate(iters.group(chunk_size, data)): |
| 1154 | + if idx == 0: |
| 1155 | + self.sendlineafter(end_markerb, "echo {} | base64 -d > {}{}".format(fiddling.b64e(chunk), compressed_path, echo_end).encode()) |
| 1156 | + 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)) |
| 1159 | + |
| 1160 | + # Decompress the file and set the permissions. |
| 1161 | + if compression_mode is not None: |
| 1162 | + self.sendlineafter(end_markerb, '{} -d {}{}'.format(compression_mode, compressed_path, echo_end).encode()) |
| 1163 | + if chmod_flags: |
| 1164 | + self.sendlineafter(end_markerb, 'chmod {} {}{}'.format(chmod_flags, target_path, echo_end).encode()) |
| 1165 | + if not prompt: |
| 1166 | + self.recvuntil(end_markerb) |
| 1167 | + |
1080 | 1168 | def connect_input(self, other): |
1081 | 1169 | """connect_input(other) |
1082 | 1170 |
|
|
0 commit comments