Post

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.

CyberChef CyberChef

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
This post is licensed under CC BY 4.0 by the author.