Jump to content
Usr6

[Hard] The Bodyguard

Recommended Posts

TheBodyguard.jpg.d2321099535641f8107d54e9ec1bc0e8.jpg

Name: TheBodyguard.jpg MD5: 1593E87EA6754E3960A43F6592CC2509

import string

alfabet = string.ascii_lowercase
alfabet2 = string.ascii_uppercase

parola = "?"

plain_text = ""
for i in parola:
    if i in alfabet:
        plain_text += i


def enc1(text):
    ret = ""
    for i in text:
        tmp = alfabet.find(i)
        if tmp != -1:
            ret += alfabet2[(tmp + 3) % 26]
    return ret


def enc2(t1, t2):
    ret = ""
    for i in range(len(t1)):
        ret += str(ord(t1[i]) + ord(t2[i])) + ","
    return ret

print "You need this:", enc2(parola, enc1(parola))

Output:

You need this:201,203,165,195,165,191,205,187,181,191,173,187,173,187,193,199,

 

  • Upvote 6
Link to comment
Share on other sites

Domnul @Usr6 mi-a trasat sarcina sa scriu write-up-ul (modul in care am rezolvat problema). Asa ca hai sa incepem.

 

 

Nivelul 0

Challenge-ul incepe cu o poza, jpeg: TheBodyguard.jpeg si cu un fisier script python (denumit de mine password_encoder.py)
Mai intai analizam poza (eu am preferat binwalk) si observam ca are concatenat la sfarsit un fisier zip:

Spoiler

64SVone.png

Avand in vedere faptul ca avem si un script python ce pare sa codeze parola, putem presupune ca arhiva atasata este parolata.
In mod normal binwalk incearca sa dezarhiveze/decomprime arhivele pe care le intalneste, dar in acest caz ne-ar incurca mai mult, asa ca vom rula binwalk --extract --carve TheBodyguard.jpg.  Acum avem un dosar _TheBodyguard.jpg.extracted unde vom gasi un fisier 14757.zip (deoarece a fost gasit la offset-ul 0x14757). Daca vom incerca sa dezarhivam acest fisier zip, ne vom lovi de necesitatea unei parole.

 

Nivelul 1

Acum vom analiza script-ul python, pentru a determina parola.
La prima vedere pare cam complicat, asa ca vom incerca sa urmarim executia de la capat la inceput.
Stim ca avem output-ul "You need this:201,203,165,195,165,191,205,187,181,191,173,187,173,187,193,199,", asa ca vom cauta codul ce genereaza aceasta linie; si il gasim pe ultimul rand al script-ului.
Observam ca apeleaza functia enc2 cu avand ca prim parametru parola noastra, iar ca al 2-lea parametru rezultatul functiei enc2.
Daca analizam corpul functiei enc2 observam ca aceasta functie concateneaza suma valorilor numerice a caracterelor de cele 2 string-uri primite ca parametrii (t1 si t2). Aici observam ca lungimea lui t1 trebuie sa fie mai mica sau egala cu lugimea lui t2. Altfel vom avea erori.
Dar t2 este de fapt parola noastra codata cu enc1. Iar in enc1 observam ca facem ROT+3 (adica "a" devine "d", "b" devine "e", etc), doar daca este o litera mica; altfel sarim peste.
Din datele de mai sus deducem ca parola este formata doar din litere mici, mai exact din 16 litere mici.

Inarmati cu aceste date vom incerca o implementare in python (ipython mai exact):

Spoiler

7FIImOs.png

Mai intai extragem sumele de caractere ca un array de intregi (In [3] in ipython).

Apoi generam toate sumele posibile ce pot rezulta din utilizarea functiei enc2 (ne intereseaza doar sumele ca valori numerice; argumentul functiei str din bucla for) si le stocam in dictionarul nostru (declarat in In [4]), impreuna cu literele aferente care au generat suma (o suma poate fi generata cu mai multe litere, cum poate fi observat in Out [8]).
La sfarsit generam toate combinatiile posibile si obtinem 8 posibile parole, dar prima pare cea mai promitatoare.
O utilizam si ne alegem cu un fisier prng, fara nici o extensie.

 

Nivelul 2

Deoarece fisierul prng nu are nici o extensie, vom apela  la utilitarul file pentru a identifica subiectul.
Aflam ca este tot o arhiva de tip zip, asa ca il redenumim in prng.zip. In momentul in care incercam sa il dezarhivam, ne lovim de o parola si un indiciu sub forma altui script python:

Spoiler

import time
import string


print """
The most powerful random password generator
\t Version 2018"""

seed = str(time.localtime()[0])
seed += str(time.localtime()[2] + time.localtime()[3])
seed += str(time.localtime()[4] + time.localtime()[5])


def password_gen(samanta):
    p = ""
    lungime = len(samanta)
    for i in range(lungime):
        if i % 2 == 0:
            p += samanta[i]
        else:
            p += string.ascii_lowercase[i]
    return p


print "Your secret password is: ", password_gen(seed)

 


De aceasta data, scriptul se vrea un pseudo random number generator, ce isi genereaza seed-ul dintr-o structura python folosita pentru stocarea datei.
Aceasta tip de date (named tuple) este strurata in felul urmator:

  • pozitia 0: anul (accesabil si prin membrul tm_year)
  • pozitia 1: luna (accesabil si prin membrul tm_mon)
  • pozitia 2: ziua lunii (accesabil si prin membrul tm_day)
  • pozitia 3: ora zile in format 24h (accesabil si prin membrul tm_hour)
  • pozitia 4: minutele (accesabil si prin membrul tm_min)
  • pozitia 5: secundele (accesabil si prin membrul tm_sec)
  • pozitia 6: ziua saptamanii (accesabil si prin membrul tm_wday)
  • pozitia 7: ziua din an (accesabil si prin membrul tm_yday)
  • pozitia 8: decalajul aferent orei de vara (accesabil si prin membrul tm_isdst)

Noi nu suntem interesati decat de pozitiile 0, 2, 3, 4, 5.
Pe de alta parte, analizand functia password_gen, observam ca pozitiile pare din string-ul seed sunt pastrate (deci sunt cifre mereu) iar pozitiile impare sunt inlocuite cu litere din alfabet, aflate la pozitia respectiva (din punct de vedere lexicografic). De aici putem deduce ca parola noastra nu poate fi mai lunga de 27 de caractere (deoarece ultimul caracter poate fi o cifra).
In continuare vom face niste supozitii legate de data folosita ca seed, si anume vom presupune ca este data crearii/modificarii fisierului din arhiva (sau foarte aproape). In fond si la urma urmei putem oricand sa facem brute-force pe parola cu un program asemanator John The Ripper, folosind ca masca pentru parola ?db?dc?df?dh?dj?dl?dn?dp?dr?dt?dv?dx?dz?d.
Daca listam continutul arhivei (folosind unzip -l prng.zip) nu vom avea decat data, ora si minutele, nu si secundele. Ca atare vom face o listare mai completa a arhivei in speranta ca vom obtine o data completa, folosind comanda unzip -Z -l -v prng.zip:

eUAiFd2.png

Inarmati cu aceste date, vom incerca un mic script python:

Spoiler

3OW3yKY.png

Mai intai declaram o functie care va calcula acea variabila seed ce se afla la inceputul script-ului original, folosind un struct_time ca argument.

Apoi copiem functia password_gen.

In ultimul rand vom crea o variabila timestruct (de tip struct_time) ce va contine data fisierului, folosind functia strptime (adica vom aplica o functie de parsing pe string-ul cu data).

Avand aceste detalii, apelam functia password_gen pentru a obtine seed-ul ce serveste ca parola.

Din nefericire aceasta parola nu a functionat, dar putem presupune ca suntem pe aproape. Daca presupunem ca nu avem secundele corecte putem genera o lista de parole (unice) pentru fiecare secunda posibila:

Spoiler

RqWuUVB.png

Incercand aceste variante, descoperim ca "2b1d3f2h" este parola potrivita, si obtimem un fisier ' .64'.

 

Nivelul 3

Fisierul obtinut la pasul anterior nu pare prea darnic in a ne prezenta vre-un indiciu; utilitarul file nu ne poate spune decat ca avem de aface cu un fisier text ASCII cu terminatii de linie CRLF (adica Windows/DOS).

La o analiza mai atenta, observam ca avem de aface cu un fisier cu mai multe linii, fiecare linie avand un numar diferit de spatii alble (caracterul ASCII 0x20). Din start am presupus ca avem de aface cu ceva base64 si ca numarul de spatii albe este relevant.

Aici m-am blocat din cauza propriei prostii si neatentii, si am pierdut cam o saptamana (adica aproximativ 10 ore distribuite de-a lungul a 7 zile).

Cand am inceput challange-ul, cel de-al 2-lea indiciu era deja postat. Asa ca m-am folosit de el. Indiciul decodat ne spune

Spoiler

Hai sa numaram:
1 caracter
2 caractere
3 caractere
...

 

Inarmat cu aceste informatii, e timpul pentru niste experimente. Mai intai citim fisierul, si il spargem in linii de spatii si observam ca avem 12 linii.

Acum sa vedem cate spatii albe avem pe fiecare linie (in poza In [5] si Out [6]). Deci nici un numar nu trece de valoarea 64, iar cum fisierul are extensia 64, indiciul a fost codat in base64 si ni s-a si precizat ca indiciul "este 2 indicii", probabil aceste numere sunt indecsii alfabetului base64.

Construim o variabila (in poza este denumita alphabet la linia In [7]) in care vom stoca alfabetul base64 (vom folosi alfabetul clasic, A-Za-z0-9+/).

Acum folosim sirul de numere de mai sus ca indecsi si... ne-am blocat. Obtinem S3+waDCjc4li, care decodat ne da urmatorul sir de bytes: 

Spoiler

00000000  4b 7f b0 68 30 a3 73 89 62                       |K.°h0£s.b|
00000009

 

Problema este extrem de simpla: noi avem niste lungimi de siruri de spatii albe, deci, pornesc de la 1, dar array-urile in python pornesc de la 0.

Si asa am pierdul degeaba o saptamana, incercand tot felul de alfabete ezoterice, si cautand alte explicatii.

Pana la urma l-am contactat pe @Usr6 care m-a trezit la realitate

Spoiler
Quote

[...] eu as zice ca e un simplu algoritm de criptare prin substitutie... [...]

 

De indata ce corectam problema index-ului obtinem solutia.

Spoiler

ECZE72F.png?1

 

Edited by u0m3
Typos
  • Upvote 6
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...