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.

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

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

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.

Korjattu
Lab 1
Binäärin vastaus on segmentation fault.
Otetaan pwndbg list main ja disassemble main

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öä.

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).

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.

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

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))

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.

Oikealla salasanalla ja hypyllä loppuun (kuten ohjelma toimiikin)

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.

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

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.

Eli r12 arvo on ’8’ ja minun ’a’ arvo tässä nyt ’-6927’.
Kokeilen myös salasanalla abcdefg, mutta huomaan seuraavalla kierroksella.

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

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