Forritunarmálið C
Dennis Ritchie þróaði forritunarmálið C á áttunda áratugnum til að endurskrifa Unix-stýrikerfið. C er þýtt yfir í smalamál, sem gerir málið hentugt til að kanna hvað gerist í vélarkóða.
Mörg bakhönnunarverkefni eru skrifuð í C og síðan þýdd yfir í vélarkóða sem er vistaður í tvíundaskrá. Til að lesa úttak bakþýðenda þurfum við því að þekkja grunnatriði C.
Minnisstjórnun
Í C sér forritarinn sjálfur um að úthluta (e. allocate) og losa (e. free) minni. Þetta er ólíkt Python og Java, þar sem þetta gerist sjálfkrafa.
int x = 5; // staðvær breyta, á stafla
int *p = malloc(4); // kvikt minni (e. dynamic memory), á hrúgu
Strengir
Strengir í C eru í raun fylki af bætum sem enda á \0, svokölluðu \0-bæti. Þetta kallast núllendaður strengur.
char name[] = "hello";
// Í minni: [ 'h', 'e', 'l', 'l', 'o', '\0' ]
// 0x68 0x65 0x6c 0x6c 0x6f 0x00
Ef strengur er geymdur án \0 geta föll á borð við printf og strlen ekki greint hvar hann endar. Þau lesa þá áfram í minni þar til þau finna næsta \0-bæti. Þetta getur leitt til upplýsingaleka.
Bendlar
Bendill er breyta sem geymir vistfang í minni, ekki gildið sjálft.
int x = 42;
int *p = &x; // p geymir vistfang x (& = "vistfang x")
printf("%d", *p); // * = aftilvísun: sækir gildið sem p bendir á → 42
Í smalamáli eru bendlar mikið notaðir. Þegar baksmalinn sýnir [rax] merkir það „gildið á vistfanginu sem rax geymir“ (sjá aftilvísun).
Gagnatög og algengar stærðir
Taflan sýnir algengar stærðir í 64-bita Linux-kerfi sem notar LP64-líkanið. Stærðir geta verið aðrar á öðrum kerfum.
| Gerð | Stærð | Bilið |
|---|---|---|
signed char |
1 bæti | -128 til 127 |
unsigned char |
1 bæti | 0 til 255 |
int |
4 bæti | -2^31 til 2^31-1 |
long / long long |
8 bæti | -2^63 til 2^63-1 |
| bendill | 8 bæti (64 bitar) | — |
Heiltöluyfirflæði
Heiltöluyfirflæði (e. integer overflow) verður þegar niðurstaða rúmast ekki í gagnataginu. Yfirflæði formerkjalausra heiltalna vefst samkvæmt reglum C, en yfirflæði formerktra heiltalna er óskilgreind hegðun nema sérstakar þýðandastillingar, til dæmis -fwrapv, skilgreini annað. Slíkar stillingar koma stundum fyrir í CTF-verkefnum.
Óörugg föll sem við leitum að
Þegar við opnum forrit í bakþýðanda leitum við að þessum föllum:
| Fall | Vandinn |
|---|---|
gets(buf) |
Les ótakmarkað magn gagna og er því alltaf óöruggt |
strcpy(dst, src) |
Afritar streng án þess að athuga lengd |
scanf("%s", buf) |
Getur lesið of langan streng ef hámarkslengd er ekki tilgreind |
sprintf(buf, fmt, ...) |
Getur skrifað út fyrir mörk buf |
printf(buf) |
Getur valdið sniðstrengsveikleika ef buf kemur frá notanda |
Þegar við sjáum eitthvert þessara falla í bakþýðanda könnum við hvort inntak notandans geti borist til þess. Ef svo er kann að vera hægt að nýta veikleika þar.
Að lesa úttak bakþýðanda
Bakþýðandi, til dæmis Ghidra, IDA eða Binary Ninja, þýðir vélarkóðann yfir í C-líkan kóða. Úttakið er ekki nákvæmur C-kóði heldur mat tólsins. Staðværar breytur liggja yfirleitt í vakningarfærslu (e. stack frame) fallsins. Hér eru algeng fyrirbæri:
// Tólin gefa ónefndum breytum óskiljanleg nöfn eins og:
undefined8 local_48; // Átta bæta stór breyta (gæti verið char[8] eða long)
long local_10; // Breyta sem liggur í vakningarfærslunni
// Þetta þýðir venjulega char[64] eða svipað:
char local_48 [64];
// Tímabundnar breytur fá oft nöfn eins og:
int iVar1; // Tímabundin breyta af gagnataginu int (Ghidra)
int v1; // Sama breyta í IDA
Endurnefnum föll og breytur
Þegar við skiljum hlutverk falls eða breytu endurnefnum við það (e. rename). Það gerir kóðann mun auðveldari að lesa. Við getum til dæmis breytt local_48 í user_input. Flýtilykillinn er L í Ghidra og N í IDA og Binary Ninja.
Æfingar
Verkefni til að æfa okkur í að lesa C-líkan kóða og fylgja því hvernig forrit vinna með gögn:
- Decoder Ring: Við skoðum einfalda umbreytingu og endurheimtum inntakið sem forritið samþykkir.
- Conveyor Belt: Við rekjum lykkju sem vinnur með hvert bæti á mismunandi hátt eftir staðsetningu þess.
- High Score: Við greinum forrit þar sem niðurstaðan er ekki eins augljós og hún virðist í fyrstu.
- Treasure Map: Við fylgjum tengingum milli gagna í minni og finnum rétta lestrarröð þeirra.