Днес четох още по въпроса. Което затвърждава мнението ми, че на 64-битова архитектура, атаките с препълвания на буфер са почти невъзможни.
Надеждите на хората там бяха насочени към една забавна техника. Понеже аргументите на функциите вече не се пазят *изцяло* в стека, а препълвайки стека ние можем да му контролираме съдържанието, те доразвиват return-into-libc техниката. За да може примерно да прескочат на system(), те трябва да заредят в регистрите указатели към стринга, който трябва да се изпълни. Та тяхният подход е следният:
Търсят инструкция RETQ, като ги интересува на какъв адрес се намира и какво има *преди* нея.
Примерно да речем имаме следната последователност:
<addr> pop %rsi
<addr+1> retq
Значи презаписваме return адреса с addr. Изпълнението продължава с pop %rsi (което взема от стека 8 байта и ги предава на регистъра RSI). Значи ако *преди* презаписания return address сме сложили някаква стойност, то тя ще се предаде на този регистър. Следва retq което на своя страна взема от стека следващият return address на който да продължим...
Значи ако на края на стека имаме:
...."/bin/sh"<return_addr_2><stoinost_na_rsi - adresa na /bin/sh><addr>
където return_addr_2 е адреса на system() в паметта (примерно), а addr е адреса на онази pop %rsi инструкция, следвана от retq.
Ние можем да контролираме стойността на RSI, която се използва от system(). Ако тя сочи към адреса на "/bin/sh" в стека, значи ние можем все пак да накараме програмата-"жертва" да ни изпълни шел.
По същият начин можем да сглобим по подходящият начин нашият стек, по начин по който да предаваме стойности и на други регистри. Разбира се в по-сложни случай нещата стават като една забавна логическа задачка.
......обаче, има 2 сериозни причини дори тази магия да е практически почти невъзможна. Първата се нарича Address Space Layout Randomization, въведена е отпреди 2.6.14 някъде и гарантира различни адреси, на които да си намериш POP.... инструкцията при всяко изпълнение на програмата. Да речем че това може да се счупи с последователни опити и брутфорсване.
Втората е нещо, което редхат-ците усилено ползват и се нарича canary value. Това е една уникална стойност, която се намира в стека *преди* return адреса. Ако някъде програмата срещне RET инструкция и тази стойност не е същата, програмата крашва.
Тези две неща правят buffer overflows под x86_64 мнооооооооооого сложна задача. Не казвам невъзможна, но ужасно сложна.
Апропо, написах и първият си x86_64 shellcode, който поради гореизброените причини надали ще е особено смислен и все пак от чисто теоретичен интерес ми беше забавно