Henry Isakoff 🥕 Sovellusten Hakkerointi

| Sovellusten Hakkerointi | 9 min

dbg, pwn ja labra

Tässä raportissa tutustumme dbg-debukkaukseen käyttäen opettajan Larin omia binäärejä. Meillä on 5 binääriä jotka meille on jaettu osana kurssimateriaaleja Haaga-Helian moodlessa - niitä en valitettavasti pysty tässä jakamaan.

Käytän tähän tehtävään tänään pwndbgtä, voisin käyttää myös ihan perus dbgtä, mutta pwndbg ollut itselläni jo käytössä aikaisemmin. Pwndbg:n oppimiskäyrä vaikuttaa loivemmalta ja sen Python 3 -pohjainen wrapperi (dbgn-lisäosa) tuo mukanaan omia etua. Kuten esittelyssä itsessään sanotaan:

”Vanilla GDB and LLDB are terrible to use for reverse engineering and exploit development. Typing x/30gx $rsp or navigating cumbersome LLDB commands is not fun and often provides minimal information. The year is 2025, and core debuggers still lack many user-friendly features such as a robust hexdump command. Windbg users are completely lost when they occasionally need to bump into GDB or LLDB.”

Eli tl;dr pwndbg on lisäosa dbglle jossa sisällä lisä-työkaluja helpottamaan debuggausta.

pwndbg

Asennus tapahtuu:

bash curl -qsL 'https://install.pwndbg.re' | sh -s -- -t pwndbg-gdb
curl -qsL 'https://install.pwndbg.re'	
Hakee skriptin URL-osoitteesta https://install.pwndbg.re ilman tulostuksia (-q, -s) ja seuraa ohjauksia (-L).
`	
| ( piippu ohjaa edellisen tuloksen seuraavaan komentoon)
sh -s -- -t pwndbg-gdb	
Suorittaa skriptin, joka on saatu curlin kautta, ja antaa sille argumentin -t pwndbg-gdb, joka kertoo skriptille, että haluat asentaa pwndbg-gdb-tyyppisen pwndbg:n.

Aloitetaan siis debuggaus.

Lab 0

Ohjelman koonnin jälkeen meillä

>./buggy_program Element 0: 1 Element 1: 2 Element 2: 3 Element 3: 4 Element 4: 5 Element 5: 0

Ajetaanpa tämä pwndbgllä, listataan main-funktio ja puretaan se osiin (disassemble).

bash pwndbg ./buggy_program bash list main bash disassemble main

Sisältö

Tästä näemme, että kommenteissa jo huomautukset. Eli puskuriylivuoto ja virheellinen koko merkattu kommenteilla.

Huomaan, että nimetty buggy_function ajetaan assemblyssä. Laitetaan break siihen:

break buggy_function run

Sisältö

Pwndbg osoittaa muistipaikalle ...555518a jossa jump if less (jl) toteutuu break-pisteessä. Ja tämä tapahtuu rivillä 4.

Kävin pitkään läpi taas ihan perus-C funkitioita ymmärtääkseni mitä tapahtuu koodissa. Ensin yritän muuttaa function (numbersi, 6), mutta ylivuoto on siis suurempi yhdellä näin. Saamme silloin uuden elementin 6 joka myös yli.

Toinen kommentti on puskuriylivuodon kohdalla, joten keskityn siihen.

for (int i = 0; i <= size; i++) { // Huomaa: <= aiheuttaa puskuriylivuodon

Eli i alkuarvo on 0, tämän jälkeen suoritetaan silmukkaa niin kauan kunnes i<= size toteutuu. Tämän jälkeen toteutetaan kierros i=i+1 (i++). Silmukka päättyy kun i++ ei ole enää tosi.

Tällä logiikalla i=5, 5<=5 on myös tosi - eli yksi kierros menee ylitse.

Kokeilen korjata koodin siis poistamalla yhtäsuuren pois <= = < jolloin viimeinen ei olisi enää totta.

Korjaus Korjaus

Korjattu

Lab 1

Binäärin vastaus on segmentation fault.

Otetaan pwndbg list main ja disassemble main

Korjaus

char * bad_message = NULL; char * good_message = "Hello, world.";

Eli tässä ohjelma näyttää ottavan kaksi merkkijonoa jotka muutetaan ja tulostetaan uudestaan. Muu vähän vielä itselle pimennossa.

Assemblykoodissa myös valistuneita arvauksia.

; Asetetaan rbp ja varataan tilaa pinosta (ymmärtääkseni ihan standardi funktion alku)
   0x117f <+0>:  push   rbp
   0x1180 <+1>:  mov    rbp,rsp
   0x1183 <+4>:  sub    rsp,0x10

; char * bad_message = NULL;
   0x1187 <+8>:  mov    QWORD PTR [rbp-0x8],0x0

; char * good_message = "Hello, world.";
; LEA (Load Effective Address) laskee "Hello, world." -merkkijonon osoitteen.
   0x118f <+16>: lea    rax,[rip+0xe6e]        # 0x2004
; Tallennetaan osoite good_message-muuttujaan pinossa.
   0x1196 <+23>: mov    QWORD PTR [rbp-0x10],rax

; print_scrambled(good_message);
; Ladataan good_message-osoitin RAX-rekisteriin.
   0x119a <+27>: mov    rax,QWORD PTR [rbp-0x10]
; Ensimmäinen argumentti (osoitin) siirretään RDI-rekisteriin.
   0x119e <+31>: mov    rdi,rax
; Kutsutaan funktiota.
   0x11a1 <+34>: call   0x1139 <print_scrambled>

; print_scrambled(bad_message);
; Ladataan bad_message-osoitin (joka on NULL) RAX-rekisteriin.
   0x11a6 <+39>: mov    rax,QWORD PTR [rbp-0x8]
; Siirretään NULL-arvo RDI-rekisteriin argumentiksi.
   0x11aa <+43>: mov    rdi,rax
; Kutsutaan funktiota toisen kerran. TÄMÄ ON VAARALLINEN KUTSU.
   0x11ad <+46>: call   0x1139 <print_scrambled>

; Loput on normaalia funktion lopetusta.
   0x11b2 <+51>: mov    eax,0x0
   0x11b7 <+56>: leave
   0x11b8 <+57>: ret

Otetaan break-pisteet kohtiin print_scrambled ja assemblyn riville +46. Ymmärtääkseni ”ehkä” ensimmäinen merkkaa myös tuon toisen, mutta tässä samalla opettelen ohjelman käyttöä.

Korjaus

05:0028│+008 0x7fffffffe568 —▸ 0x5555555551a6 (main+39) ◂— mov rax, qword ptr [rbp - 8] 06:0030│+010 0x7fffffffe570 —▸ 0x555555556004 ◂— 'Hello, world.' 07:0038│+018 0x7fffffffe578 ◂— 0

Ensimmäinen osuus printtaa ”Hello, world” setin oikein. Hypätään seuraavaan ’C’.

Seuraavassa näemme, että olemme kohdassa juuri ennen kuin bad_message komento ajetaan (main+46).

Korjaus

Hypätään askel si

0x55555555514f <print_scrambled+22> movzx eax, byte ptr [rax] <Cannot dereference [0]>

Näen tämän joten jatkan hyppimistä kunnes hyppään yli. Ja saan viestin ongelmasta.

Korjaus

Eli message=0x0 johtaa segmentaatiovirheeseen. Meidän pitää korjata tuo. Lisätään koodiin pätkä jossa annetaan arvolle message=null.

    if (message == NULL)
    {
        printf("Error: NULL on tyhjä!.\n");
        return;
    }

Ja korjattuna

Korjaus

Lab 2

Labrassa 2, meillä taas käytössä passtr ja sisällä myös passtr2o. Tehtävänä on löytää lippu (tai liput).

Eli otetaan taas disassemble main. Nyt näemme, että voidaan ottaa break main. Vaikka näemme lipun tässä suoraan - haetaan se pwndbgstä hyppäämällä.

Break-main koodissa näemme, että IF osuus hakee strcmp-funktion joka löytyy assemblystä riviltä 67. Siihen breikki (break *(main + 67))

Korjaus Korjaus

Otetaan run ja syötetään testisalasana. Nyt näemme toteutuneessa koodissa RDX arvon johon tätä verrataan ja siellä oikea salasana jolla voimme avata lipun. Testinä run uudestaan ja käytetään oikeaa salasanaa. Nyt lippu avautuu main+86 arvossa.

Korjaus

Oikealla salasanalla ja hypyllä loppuun (kuten ohjelma toimiikin)

Korjaus

Tehtävä: 2o

List main ei löytänyt mitään. Eli lähdekoodia ei nyt ole saatavilla. Kaikki pitää lukea assemblystä. Disassemble löysi call funktioita joista pari näyttävät standardin ulkopuolisilta -> lisätään ne breakeiksi.

Breikit

break mAsdf3a break EaseEAs

Assemblystä huomaan myös pari perusfunktiota. Eli isoc99 funktio on toivottavasti käyttäjän syöte. Call EaseEAs on ennen print funktiota, eli siellä on todennäköisesti meidän salauskaava/salasana tjms. mAsdf3a ajetaan ennen mainin loppua, joten voidaan olettaa sijaitsevan jotain tärkeää.

Ajetaan ihan vaan run, testataan salasanalla a ja tämän jälkeen disassemble mAsdf3a

Rikotaan

mAsdf3a huomataan, että bittejä siirretään osuudessa 45 ja 50. Funktiosta myös vähennetään 7 merkkiä ja lisätään 3 merkkiä. Välissä myös testataan 0x1.

Ajan tämän mistrallin läpi

Tämä koodiosuus näyttää olevan osa tietorakenteen käsittelyä, jossa: Lukee ja laajentaa tavut rbp ja rbx osoittamista muistialueista. Tarkistaa, onko al pariton tai parillinen. Muokkaa edx:n arvoa riippuen siitä, onko al pariton tai parillinen: Jos al on pariton, vähennetään edx:stä 7. Jos al on parillinen, lisätään edx:ään 3. Vertaa lopullista edx:n arvoa ecx:n kanssa.

Eli tästä teen valistuneen arvauksen, että mAsdf3a on salasana ja sitä ei verrata mihinkään valmiiseen listaan = se generoidaan matkalla. Tärkeä osuus on, että parittomasta luvusta vähennetään 7 ja parilliseen lisätään 3.

Mutta mihin arvoon? Ja monta?

Katson itseasiassa, että +34 tehdään edx vertaus arvoon r12d. Eli voidaan katsoa noita arvoja. Hyppään n näppäimellä kunnes meille avataan arvo r12.

Arvot

Eli r12 arvo on ’8’ ja minun ’a’ arvo tässä nyt ’-6927’.

Kokeilen myös salasanalla abcdefg, mutta huomaan seuraavalla kierroksella.

Arvot

mAsdf3a +3 RBP arvon = anLTj4u8. En ymmärrä tätä ollenkaan - kokeillaanpa kääntää ascii käännöksellä.

ASCII Tehty

Tämä ratkesi sillä. Epäilen, myös, että tässä minulla pwndbg auttoi ratkaisemaan tämän. Näen suoraan, että alkuarvo on d=100 ja siitä arvot vaihtelevat määrämerkkiin 8 asti, jolloin salasana on sama.

Kommentit

Pwndbg on oiva työkalu debuggaukseen. Itselläni on vasta tutustumiskäyrä alussa C:n kanssa, mutta nämä ovat oikein kivoja tapoja käydä läpi kokonaisuutta.

Lähteet

Pwndbg https://pwndbg.re

Dokumentissa käytetty apuna C-koodin ymmärryksessä ja dbg perusfunktioiden opettelussa LLM mistra-small3.2:24b mallia. Rautana käytössä ollut Macbook pro m2 max 64gb ja kääntäjänä LM-studio. https://huggingface.co/mistralai/Mistral-Small-3.2-24B-Instruct-2506 https://lmstudio.ai

Kuvat optimoitu https://optimage.app

Käytetty aika 4h 10min