Funktiot ja kutsupinot
Funktiot ja kutsupinot
Tähän asti ohjelmamme ovat olleet lineaarisia rivilistauksia. Oikeat ohjelmat jakavat työn funktioihin (aliohjelmiin), joita kutsutaan eri puolilta ohjelmaa. Assemblyssa funktiokutsut rakentuvat kahdesta peruskomponentista: CALL/RET-parista ja kutsupinosta (call stack), jolle laitetaan parametrit, paluuosoite (return address) ja paikallismuuttujat.
Kutsupinon kertaus
Pino kasvaa muistissa ylhäältä alaspäin: PUSH vie ESP-rekisteriä pienempään osoitteeseen ja POP päinvastoin. ESP osoittaa aina viimeksi pinotun arvon paikkaan.
1PUSH 100 ; ESP -= 4, talleta 100 muistiin osoitteeseen ESP
2PUSH 200 ; ESP -= 4, talleta 200
3POP EBX ; EBX = 200, ESP += 4
4POP EAX ; EAX = 100, ESP += 4CALL ja RET
CALL label tekee kaksi asiaa: pinoaa paluuosoitteen (sen rivin osoitteen, joka tulee CALLin jälkeen) ja sitten hyppää labeliin. Kun funktio ajaa RET:n, paluuosoite poppataan pinosta ja suoritus jatkuu siitä.
Yllä oleva ohjelma asettaa EAX=1234 funktiossa, palaa kutsujalle, tulostaa luvun ja lopettaa HLT:llä. Tärkeä huomio: ilman HLT:tä suoritus jatkuisi pääohjelman jälkeen funktion määrittelyyn, mikä ei ole tarkoitus.
Parametrien välitys pinon kautta
Funktiolle annetaan parametrit pinoamalla ne ennen CALLia, oikealta vasemmalle (eli viimeisin parametri pinotaan ensin). Tätä noudattaa cdecl -kutsutapaa, joka on yleinen 32-bittisessä x86-koodissa. Paluuarvo jätetään yleensä EAX-rekisteriin.
Stack frame ja EBP
Funktion sisällä on tapana lukita viittauspiste pinoon EBP-rekisterillä: paikallismuuttujat ja parametrit luetaan suhteessa siihen, ei jatkuvasti liikkuvaan ESP:hen.
Prologue ja epilogue ovat kahden käskyn rituaaleja:
![Pinokehys prologuen jälkeen: paikallismuuttujat, vanha EBP, paluuosoite ja parametrit osoitteissa [EBP+8] ja siitä eteenpäin.](https://cdn.sanity.io/images/3unppnk2/ha-content/2eb8b158bbffd45bbfbc39c2eda89b53e01ca767-1536x1024.png?w=3840&q=75&fit=clip&auto=format)
- Prologue (funktion alku):
X86
1PUSH EBP ; tallenna kutsujan EBP pinoon 2MOV EBP, ESP ; ankkuroi EBP nykyiseen pinon huippuun - Epilogue (funktion loppu):
X86
1MOV ESP, EBP ; palauta pinon huippu prologuessa olleeseen tilaan 2POP EBP ; palauta kutsujan EBP
Epiloguen sijaan disassemblyssä näkee usein yksinkertaisen LEAVE-käskyn: se on tasan MOV ESP, EBP + POP EBP. Prologue puolestaan kirjoitetaan aina PUSH EBP; MOV EBP, ESP eksplisiittisesti. (x86:lla on myös yhdistetty ENTER imm16, imm8 -käsky, joka pinoaa kehyksen ja varaa imm16 tavua paikallisille. Se on kuitenkin mikrokoodattu ja hidas, joten modernit kääntäjät eivät emittoi sitä.)
Esimerkki: kahden luvun yhteenlasku-funktio
Aja ohjelma rivi kerrallaan emulaattorissa ja seuraa, miten ESP ja EBP liikkuvat. Pinon arvot ovat luettavissa muistinäkymässä; huomaat itse, miten paluuosoite ja parametrit asettuvat prologuen (PUSH EBP; MOV EBP, ESP) jälkeen.
Miksi siivota parametrit?
cdecl:ssä kutsuja vastaa parametrien siivoamisesta pinosta CALLin jälkeen. ADD ESP, 8 kahdelle 32-bittiselle parametrille. Ilman siivoamista pino kasvaisi joka kutsulla ja luultavasti vuotaisi yli kohtuullisen pian.
Reverse engineeringissä törmäät myös stdcall:iin (käytetty Win32 API:ssa), jossa funktio itse siivoaa parametrit RET N -muodolla, sekä modernimpiin rekistereihin perustuviin kutsutapoihin.
Hakkeroinnin oppiminen alkaa tästä
Sadat interaktiiviset kurssit, virtuaalilabrat ja CTF-haasteet selaimessasi. Aloita ilmainen kokeilu ilman korttitietoja.