Kynning á bakhönnun
Bakhönnun (e. reverse engineering) snýst um að reyna að skilja hvað forrit gerir án þess að hafa frumkóðann. Í CTF-keppnum fáum við oftast tvíundaskrá og markmið okkar er að finna fánann sem forritið felur.
Þrjár tegundir bakhönnunarverkefna
1. Þýdd tvíundaskrá (C/C++)
Við höfum ekkert nema vélarkóða. Við notum baksmala eða bakþýðanda til að lesa hann.
2. Þýddur bætakóði (Java, .NET, Python .pyc)
Forritið er þýtt í bætakóða sem keyrir í sýndarvél. Oftast er auðveldara að bakþýða bætakóða en vélarkóða sem ætlaður er tilteknum örgjörva.
3. Torlæs frumkóði (Python, JavaScript)
Hér höfum við frumkóðann sjálfan en honum hefur vísvitandi verið breytt svo hann verði torlæs, til dæmis með óskiljanlegum breytunöfnum eða földum strengjum sem eru afkóðaðir á keyrslutíma.
Fyrstu skref
Þegar við fáum tvíundaskrá í CTF-verkefni er þetta góð leið til að byrja:
1. Könnum skráargerðina
file challenge
# challenge: ELF 64-bit LSB executable, x86-64, dynamically linked
Skipunin file sýnir okkur gerð skrárinnar, til dæmis hvort hún sé ELF-skrá fyrir Linux, PE-skrá fyrir Windows eða af allt annarri gerð.
2. Leitum að strengjum
strings challenge | less
Oft er hægt að finna gagnlega strengi sem gefa hugmynd um hvað forritið gerir.
3. Prófum keyrslu í öruggu umhverfi
chmod +x ./challenge
./challenge
Hvað gerir forritið? Biður það um lykilorð eða fána? Við prófum mismunandi inntak og könnum hvernig forritið bregst við.
Keyrum ekki óþekktar skrár í blindni
Skrár af netinu geta verið spilliforrit. Öruggast er að keyra þær í sérstakri sýndarvél sem geymir engin mikilvæg gögn. Að notkun lokinni getum við endurheimt vélina í fyrra ástand eða eytt henni.
4. Opnum skrána í bakþýðanda
Við opnum skrána í bakþýðanda, til dæmis Ghidra, IDA eða Binary Ninja, og byrjum á fallinu main. Bakþýðandinn umbreytir vélarkóðanum í C-líkan kóða sem er mun auðveldara að lesa. Í kaflanum um tól til kyrrstæðrar greiningar berum við tólin saman.
5. Skrifum lausnina
Þegar við skiljum hvernig forritið metur inntakið getum við fundið rétta svarið. Oftast er þægilegast að skrifa stutta Python-skriftu sem snýr útreikningunum við en í einföldum dæmum getur verið fljótlegra að reikna svarið í höndunum.
Hvernig lítur einfalt verkefni út?
Mörg bakhönnunarverkefni fylgja sama grunnmynstri í bakþýðanda. Þegar við lærum að þekkja mynstrið verðum við fljótari að átta okkur á nýjum verkefnum:
// Dæmigert bakhönnunarverkefni
void main(void) {
char input[64];
printf("Sláðu inn fánann: ");
fgets(input, 64, stdin);
if (check(input)) {
puts("Rétt!");
} else {
puts("Rangt!");
}
}
int check(char *input) {
// Forritið ber inntakið saman við eitthvað...
return strcmp(input, "leynin\n") == 0;
}
Til að leysa verkefnið þurfum við að rekja hvernig forritið ákveður hvort inntak sé rétt. Oft er best að byrja á lokaniðurstöðunni og vinna sig aftur á bak: Við hvað er inntakið borið saman og hvaða breytingar eru gerðar á því fyrst?
Samanburðurinn þarf ekki að vera í sérstöku falli eins og check(). Þýðandinn getur fellt hann inn í main eða dreift ferlinu yfir mörg föll. Verkefnið er samt það sama: að rekja leiðina frá inntaki, í gegnum hugsanlegar umbreytingar, að samanburðinum sem samþykkir það eða hafnar því.
Algengar aðgerðir
Hér skoðum við nokkrar af algengustu aðgerðunum með stuttum dæmum.
XOR
Misgildisaðgerð (e. exclusive OR, XOR) er bitaaðgerð sem er algeng í dulritun. Hún er andhverfanleg: ef við beitum aðgerðinni aftur með sama lykli fáum við upprunalega gildið. Sami lykill getur því bæði dulritað og dulráðið gögnin.
// key og target eru bæði geymd í .rodata
char key = 0x42;
unsigned char target[7] = { 0x25, 0x25, 0x39, 0x30, 0x27, 0x34, 0x3f };
char input[8];
fgets(input, sizeof(input), stdin);
int correct = 1;
for (int i = 0; i < 7; i++)
if ((input[i] ^ key) != target[i]) // Beitir XOR-aðgerð á hvert bæti og ber saman
correct = 0;
// correct er 1 aðeins ef dulritað inntak samsvarar target
Þar sem XOR-aðgerðin er andhverfanleg dulráðum við target með því að beita henni aftur með sama lykli, til dæmis með stuttri Python-skriftu:
target = bytes([0x25, 0x25, 0x39, 0x30, 0x27, 0x34, 0x3f])
key = 0x42
flag = bytes(b ^ key for b in target)
print(flag.decode()) # gg{rev}
XOR-aðgerðin er sín eigin andhverfa
Ef a XOR k = b, þá er b XOR k = a. Við getum því dulráðið gögnin ef við þekkjum lykilinn. Þekktur hluti ódulritaða textans getur einnig hjálpað okkur að finna samsvarandi hluta lykilsins.
Samanburður
Samanburður felst hér í því að forritið ber inntakið saman við rétt svar, annaðhvort í einu lagi með strcmp eða bæti fyrir bæti í lykkju.
char target[] = "gg{l3yn1lykill}";
int correct = 1;
for (int i = 0; i < len; i++)
if (input[i] != target[i])
correct = 0; // eitt rangt bæti dugar til að fella allt
// correct er 1 aðeins ef öll bætin samsvara target
Uppfletting í töflu
Við notum uppflettingartöflu þegar hvert gildi í inntakinu er notað sem vísitala í fastri töflu til að fá annað gildi. Þetta er sama grunnhugmynd og í umskiptadulmáli.
// tafla og target eru bæði geymd í .rodata
unsigned char tafla[16] = {
0x37, 0x8a, 0x2c, 0x91, 0x4f, 0xe3, 0x10, 0x66,
0xb5, 0x09, 0xdd, 0x72, 0xa8, 0x3e, 0xc1, 0x5b
};
unsigned char target[4] = { 0x8a, 0x91, 0x91, 0x66 };
int input[4];
int valid = 1;
for (int i = 0; i < 4; i++)
if (scanf("%d", &input[i]) != 1 || input[i] < 0 || input[i] >= 16)
valid = 0; // aðeins vísitölur 0–15 eru gildar
int correct = valid;
if (valid)
for (int i = 0; i < 4; i++)
if (tafla[input[i]] != target[i]) // flettir hverri tölu upp og ber saman
correct = 0;
// correct er 1 aðeins ef hvert uppflett gildi samsvarar target
Þar sem bæði tafla og rétta úttakið target liggja fyrir getum við leyst dæmið. Fyrir hvert gildi í target finnum við vísitöluna í tafla sem skilar því. Gildin 0x8a 0x91 0x91 0x66 koma úr sætum 1, 3, 3, 7, svo rétta inntakið er 1 3 3 7.
Bitaaðgerðir
Bitaaðgerðir (e. bitwise operations) vinna með einstaka bita í tölu. Algengustu virkjarnir í C eru:
| Virki | Aðgerð | Niðurstaða fyrir hvern bita |
|---|---|---|
~ |
Neitun (e. NOT) | Snýr bitanum við: 0 verður 1 og 1 verður 0 |
& |
Ogun (e. AND) | Verður 1 aðeins ef báðir bitarnir eru 1 |
| |
Eðun (e. OR) | Verður 1 ef annar eða báðir bitarnir eru 1 |
^ |
Misgildisaðgerð (e. XOR) | Verður 1 ef bitarnir eru ólíkir |
<<, >> |
Bitastjak (e. bit shift) | Færir alla bitana til vinstri eða hægri |
Oft eru nokkrar þessara aðgerða notaðar saman til að færa, velja eða sameina bita. Í næsta dæmi skiptum við á efri og neðri fjórum bitum hvers bætis:
// Hvert bæti er bitavíxlað: efri og neðri fjórir bitarnir skipta um sæti
unsigned char target[3] = { 0x27, 0x56, 0x67 };
char input[8];
fgets(input, sizeof(input), stdin);
int correct = 1;
for (int i = 0; i < 3; i++) {
unsigned char b = (unsigned char)input[i];
unsigned char swapped = (b << 4) | (b >> 4);
if (swapped != target[i])
correct = 0;
}
// correct er 1 aðeins ef bitavíxlað inntak samsvarar target
Vinstri stjakið b << 4 færir neðri fjóra bitana upp í efri helming bætisins og hægri stjakið b >> 4 færir efri fjóra bitana niður. Eðunin | sameinar síðan helmingana í eitt bæti. Til dæmis verður 0x72, eða 0111 0010 í tvíundakerfi, að 0010 0111, sem er 0x27.
Bitavíxlunin er sín eigin andhverfa, svo við endurheimtum inntakið með því að víxla bitunum í target aftur: 0x27 0x56 0x67 verða 0x72 0x65 0x76, sem eru ASCII-gildin fyrir rev.
Gagnlegt veftól
- godbolt.org: Þar getum við skrifað C-kóða og séð smalakóðann samstundis. Það hjálpar okkur að tengja C-kóða við smalamál.
Tengdir kaflar
- Forritunarmálið C: Grunnatriði málsins sem mörg bakhönnunarverkefni eru þýdd úr.
- Smalamál: Málið sem við lesum þegar við skoðum vélarkóðann.
- ELF-sniðið: Uppbygging keyranlegra skráa á Linux.
- Tól til kyrrstæðrar greiningar: Bakþýðendur,
stringsogobjdump. - Tól til kvikrar greiningar: GDB,
ltraceogstrace. - Bakhönnun netforrita: Biðlarar, þjónar og samskiptareglur.
- Gagnaskipanir: Að þekkja tré, lista og töflur í minni.
- Python-verkefni: Bakhönnun Python-kóða sem hefur verið gerður torlæs.
- Sýndarvélar og stýriflæði: Sérsniðin skipanasett og kóði sem hefur verið gerður torlæs.
Æfingar
Við getum prófað þessi verkefni til að æfa grunnvinnulag kaflans:
- Plain Sight: Við kynnumst grunnverkfærunum með einfaldri tvíundaskrá.
- Jigsaw: Við berum saman það sem kyrrstæð greining sýnir og það sem gerist þegar forritið keyrir.
- Open Sesame: Við greinum einfalt sannprófunarforrit og finnum inntakið sem það samþykkir.