Hackfest 2023: Phone to the Future
Writeup for the “Phone to the Future” challenge created by muemmemlmoehre for the Hackfest CTF 2023.
For this challenge, the file mobile.apk
is provided.
01 - Dig in
The first step is to decompile the Android application using a software such as JADX
:
1
% jadx mobile1.apk
With a simple search, we can easily find the first flag located in the AndroidManifest.xml
:
1
2
% grep "HF-" -r mobile1/
mobile1/resources/AndroidManifest.xml: <meta-data android:name="nothing_to_see_here" android:value="HF-688b7afd8d75a548a2c7f2c7b10748f1"/>
02 - Phones just wanna call home
By examining the decompiled source code, we found this interesting method that allows the application to communicate via HTTP with a server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static final void onCreate$lambda$3(MainActivity this$0, View it) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
final TextView textView = (TextView) this$0.findViewById(R.id.textView2);
RequestQueue queue = Volley.newRequestQueue(this$0);
Intrinsics.checkNotNullExpressionValue(queue, "newRequestQueue(...)");
StringRequest stringRequest = new StringRequest(0, "http://timedash.delorean.fut:1985/secret?futurespeedrailfence=W2NpaGVGdGFhdGhudGV6ckgtc3JmeGx0Zyp5Km8qMW51dGV5eSFlP2QqP2RvY2UqKm4qciJuc241Y3RyZDFhVGEhYWVhNSppKmU6KmgqKipjZTlsb0RyMmVlbFkub3JhZW5vcjB0KmxvTW5vZGUqY3UqYW1ldS5kTDV0bSpjQXJvaCpBc2U2c2FoYy5nbCpzKixENSpkYW9NZWVlZTEqKjhzZXMqLnRjaGUyd2FkcyoqdCpudXQsOmUqNWV0c25haWwqKjEqZjJsaHVlbnNhbkUqc283bmVjbWRpcmVpeWgqYmkqY2UqZCpobmxhdDFhZmVjeipzV3NlbHU5dGxzYWV0dCJ0c2xvOHN1c2xyJ3IqZWkqKmEqeGZwb251LGljYy40ZSp1cypkY3NuZWEuZmhkbGlzaXRpKnJ0LjV0aWxkZWR1KmhwY2U2KnN5KmMqcm5hKmhuNyxwKmxvSWVvc3QqaWNzZXNhbioqaSphdWg3ZXJlcmRdb3RqKnBjNGRzbm9zc2ZzdWQqYWNpYXRwIWUqZXNud205c2wqbSp0YnV0QWkqOGUtRWVNb29xKip0ZTFCLWl0YW50KmIuaG0qKipuKnIqaGVldCppRD9bc2V0bip0Y2NodG9laHRoeXdFYW9haSpjbGllVDpvaWlteG1hOnlzaSoqZG5yZWUqKip0Km4hQSpzcCoqYXRUc3cqUmhzdG90ZW5saCphdFUsZWVyaGJkaWVldG9PKmtpcGUqKnUqbWMqSEphbnAqb3Rid29odCpldCphd3RoKmFzKmhSc1thKm8qZXV5KmJlRXUqbmVyZSpvKmhlKlBzIWRobHJ0eUl0ZWYqKnkqVGR1aSoqaXB1U0N0dCondG1lc3dzdEVocmg6c3VlbWUqXXVMcmFlYypmKipldCpySWlNKm9mKm0nKmlMZU1zKmNEaWVhbmkqbyoqdCxhKnJoY2l0b29vOCFuciFzdGhsLGRrbjgqdyo/dCppbCoqKioqSm9heSpvbmVpdG9oIWVkcmV0dGV0Zm91aT9zKmVoaW4hKipudHN1dW0qdG1pKnV5KiEqb3NsYyplKk1vb3kqRHkqYW9lKmVheXVoZSpDQ21ydHRyKid3TGxoKnBhcnV0ZXIqb2xyOmwqYW55cmUscmVpY2Vsdmk6YSpyZXRzb3RsZW0qKmdhYSp0RGVlbCpXLG9jbkksKmxoZWVhaGkqKioqIXkqcm5pdW5hdGREbiplIU90KmcqaWlvaWloKioqLCpvbWRjZW50SS5hY3R0ZSosdHQqKmUqb29uKnQqc2Flc3JtRCppbWF5bmNyZXVpKmIqYWhvaXRlbnRuZXVlY1d1RS5odHV1dGluaCoqKip3KmZ0dWxpaSFkZE0qaCplbmRobmFpZWEsaWUuaSpjZUhzdHJubWgqbWFhXSppYXRlKnRXKiptKjpucnloaSphYXQqRGN0ZzpUbm9pKmllb2UqdHRt", new Response.Listener() { // from class: ca.hfctf.mobile1.MainActivity$$ExternalSyntheticLambda2
@Override // com.android.volley.Response.Listener
public final void onResponse(Object obj) {
MainActivity.onCreate$lambda$3$lambda$1(textView, (String) obj);
}
}, new Response.ErrorListener() { // from class: ca.hfctf.mobile1.MainActivity$$ExternalSyntheticLambda3
@Override // com.android.volley.Response.ErrorListener
public final void onErrorResponse(VolleyError volleyError) {
MainActivity.onCreate$lambda$3$lambda$2(textView, volleyError);
}
});
queue.add(stringRequest);
Toast popUp = Toast.makeText(this$0, "Phoned home to server.", 0);
popUp.show();
}
The HTTP parameter futurespeedrailfence
is a message encoded in base64.
1
W2NpaGVGdGFhdGhudGV6ckgtc3JmeGx0Zyp5Km8qMW51dGV5eSFlP2QqP2RvY2UqKm4qciJuc241Y3RyZDFhVGEhYWVhNSppKmU6KmgqKipjZTlsb0RyMmVlbFkub3JhZW5vcjB0KmxvTW5vZGUqY3UqYW1ldS5kTDV0bSpjQXJvaCpBc2U2c2FoYy5nbCpzKixENSpkYW9NZWVlZTEqKjhzZXMqLnRjaGUyd2FkcyoqdCpudXQsOmUqNWV0c25haWwqKjEqZjJsaHVlbnNhbkUqc283bmVjbWRpcmVpeWgqYmkqY2UqZCpobmxhdDFhZmVjeipzV3NlbHU5dGxzYWV0dCJ0c2xvOHN1c2xyJ3IqZWkqKmEqeGZwb251LGljYy40ZSp1cypkY3NuZWEuZmhkbGlzaXRpKnJ0LjV0aWxkZWR1KmhwY2U2KnN5KmMqcm5hKmhuNyxwKmxvSWVvc3QqaWNzZXNhbioqaSphdWg3ZXJlcmRdb3RqKnBjNGRzbm9zc2ZzdWQqYWNpYXRwIWUqZXNud205c2wqbSp0YnV0QWkqOGUtRWVNb29xKip0ZTFCLWl0YW50KmIuaG0qKipuKnIqaGVldCppRD9bc2V0bip0Y2NodG9laHRoeXdFYW9haSpjbGllVDpvaWlteG1hOnlzaSoqZG5yZWUqKip0Km4hQSpzcCoqYXRUc3cqUmhzdG90ZW5saCphdFUsZWVyaGJkaWVldG9PKmtpcGUqKnUqbWMqSEphbnAqb3Rid29odCpldCphd3RoKmFzKmhSc1thKm8qZXV5KmJlRXUqbmVyZSpvKmhlKlBzIWRobHJ0eUl0ZWYqKnkqVGR1aSoqaXB1U0N0dCondG1lc3dzdEVocmg6c3VlbWUqXXVMcmFlYypmKipldCpySWlNKm9mKm0nKmlMZU1zKmNEaWVhbmkqbyoqdCxhKnJoY2l0b29vOCFuciFzdGhsLGRrbjgqdyo/dCppbCoqKioqSm9heSpvbmVpdG9oIWVkcmV0dGV0Zm91aT9zKmVoaW4hKipudHN1dW0qdG1pKnV5KiEqb3NsYyplKk1vb3kqRHkqYW9lKmVheXVoZSpDQ21ydHRyKid3TGxoKnBhcnV0ZXIqb2xyOmwqYW55cmUscmVpY2Vsdmk6YSpyZXRzb3RsZW0qKmdhYSp0RGVlbCpXLG9jbkksKmxoZWVhaGkqKioqIXkqcm5pdW5hdGREbiplIU90KmcqaWlvaWloKioqLCpvbWRjZW50SS5hY3R0ZSosdHQqKmUqb29uKnQqc2Flc3JtRCppbWF5bmNyZXVpKmIqYWhvaXRlbnRuZXVlY1d1RS5odHV1dGluaCoqKip3KmZ0dWxpaSFkZE0qaCplbmRobmFpZWEsaWUuaSpjZUhzdHJubWgqbWFhXSppYXRlKnRXKiptKjpucnloaSphYXQqRGN0ZzpUbm9pKmllb2UqdHRt
Once decoded, we obtain this message, which appears to be a cipher.
1
[ciheFtaathntezrH-srfxltg*y*o*1nuteyy!e?d*?doce**n*r"nsn5ctrd1aTa!aea5*i*e:*h***ce9loDr2eelY.oraenor0t*loMnode*cu*ameu.dL5tm*cAroh*Ase6sahc.gl*s*,D5*daoMeeee1**8ses*.tche2wads**t*nut,:e*5etsnail**1*f2lhuensanE*so7necmdireiyh*bi*ce*d*hnlat1afecz*sWselu9tlsaett"tslo8suslr'r*ei**a*xfponu,icc.4e*us*dcsnea.fhdlisiti*rt.5tildedu*hpce6*sy*c*rna*hn7,p*loIeost*icsesan**i*auh7ererd]otj*pc4dsnossfsud*aciatp!e*esnwm9sl*m*tbutAi*8e-EeMooq**te1B-itant*b.hm***n*r*heet*iD?[setn*tcchtoehthywEaoai*clieT:oiimxma:ysi**dnree***t*n!A*sp**atTsw*Rhstotenlh*atU,eerhbdieetoO*kipe**u*mc*HJanp*otbwoht*et*awth*as*hRs[a*o*euy*beEu*nere*o*he*Ps!dhlrtyItef**y*Tdui**ipuSCtt*'tmeswstEhrh:sueme*]uLraec*f**et*rIiM*of*m'*iLeMs*cDieani*o**t,a*rhcitooo8!nr!sthl,dkn8*w*?t*il*****Joay*oneitoh!edrettetfoui?s*ehin!**ntsuum*tmi*uy*!*oslc*e*Mooy*Dy*aoe*eayuhe*CCmrttr*'wLlh*paruter*olr:l*anyre,reicelvi:a*retsotlem**gaa*tDeel*W,ocnI,*lheeahi****!y*rniunatdDn*e!Ot*g*iioiih***,*omdcentI.actte*,tt**e*oon*t*saesrmD*imayncreui*b*ahoitentneuecWuE.htuutinh****w*ftulii!ddM*h*endhnaiea,ie.i*ceHstrnmh*maa]*iate*tW**m*:nryhi*aat*Dctg:Tnoi*ieoe*ttm
The Cipher Identifier tool from dcode
indicates that the message appears to be encoded with a Skip Cipher. Using their Skip Cipher decoder while ensuring not to skip punctuation characters, we obtain several messages, one of which appears to contain English text.
1
[ey?"!*You*see,*Einstein*has*just*become*the*world's*first*time*ttr*an*Din*suntlhe*ngtng!*The*molecular*structure*of*both*Einstein*and*the*car*are*corrn:*o*tictarnutn*a*c*tly*1:20*A.M.*and*zero*seconds!*Marty:*Ah,*Jesus*Christ!*Jesus*Cp*vmWhn*m**nth**.m**oafter*Doc*has*successfully*sent*Einstein*to*the*future*on*his*D*h:ellei**t*aoE**ihWaiFsnoc*leets*sselniats*eht*,sediseB*?elyts*emos*htiw*ti*od*ton*yhLlioehrO*coiaW*dan**ihH*?naeroLeD*a*fo*tuo*...enihcam*emit*a*tliub*uoy*em*'nillet*uoy'*rtt*ye*.*Dbeh!itthncz*dna*.M.A*12:1*ylesicerp*ta*dnA*.tcaxe*eb*ot*erutuf*eht*otni*ereeraI*nit*rieiinHir:mt*era*lleh*eht*nehW"*,si*noitseuq*etairporppa*ehT*:coD*!?yeht*etuyagc*doeteetuudc]:tthtyna*etargetnisid*t'ndid*I*]seton*nwod*sekat[*!ytraM*,nwod*mlamaai*,iaid,scetfeiamDeaxe*derrucco*tnemecalpsid*laropmet*ehT*!RUOH*REP*SELIM*88*!?uoyC*lle*augoetyi.whe**tetruction*made*the*flux*dispersal--*[his*watch*beeps]*Look*out!*elrcteent,tnmhu*M,mta*e-1d559ad5658d527b198a4f567c74c981*Doc:*The*way*I*see*it,*if*youwoesDl*!*ao**c*dereioiro*seconds,*we*shall*catch*up*with*him*and*the*time*machine!*Ma*r,e*,!*hIem*uniasayT
The message doesn’t seem to be fully decoded, but it is possible to identify a part of the flag -1d559ad5658d527b198a4f567c74c981
. To reconstruct the final flag, you just need to add the correct prefix.
1
HF-1d559ad5658d527b198a4f567c74c981
03 - Time is in flux… Flogs… Flags
The final flag can be found in this method, still originating from the decompiled code.
1
2
3
4
5
6
7
public static final void onCreate$lambda$0(MainActivity this$0, View it) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Toast popUp = Toast.makeText(this$0, "Sorry, no flag here.", 0);
popUp.show();
Log.i("I think I left something here...", "GilMBhINYl0GRVNvVzcBdltQCwIxBidcXhd9AwMGNAsCAkE=");
Log.w("code review comment", "Flag redacted. No sensitive data in PROD!!");
}
The flag is encoded in base64, but once decoded, you obtain only binary data. Another function in the application hints that the flag might be encrypted using an XOR function, but the key is not present in the application.
1
2
3
4
5
6
7
8
9
10
11
12
13
private static final String onCreate$xorStrings(String str1, String str2) {
StringBuilder result = new StringBuilder();
int length = str1.length();
for (int i = 0; i < length; i++) {
int char1 = str1.charAt(i);
int char2 = str2.charAt(i);
int xorResult = char1 ^ char2;
result.append((char) xorResult);
}
String sb = result.toString();
Intrinsics.checkNotNullExpressionValue(sb, "toString(...)");
return sb;
}
Since we know that the flags start with HF-
, we can determine the start of the encryption key by xoring the start of the flag with the binary value.
We then obtain that the first three characters of the key are Roa
. Given that the theme of the CTF is the Back to the Future trilogy, we can deduce that the key is the famous line from Doc:
Roads? Where we’re going we don’t need roads
With a bit of trial and error, we eventually obtain a string that resembles a flag with this key
1
Roads?WhereWeReGoingWeDontNeedRoads
1
HF-ba255c7682ed149eefcc30c3ffbfdcf2