Препълване на буфер, което презаписва локални променливи

Правя упражнение за препълване на буфер, където е даден изходният код. Упражнението ви позволява да промените броя на аргументните вектори, които подавате в програмата, така че да можете да заобиколите нулевия проблем, което го прави лесно.

Упражнението обаче също така споменава, че е възможно да се използва само 1 вектор на аргумент за компрометиране на този код. Любопитен съм да видя как може да стане това. Всякакви идеи как да подходим към това ще бъдат много оценени.

Проблемът тук е, че дължината трябва да бъде презаписана, за да се осъществи препълването и адресът за връщане да бъде компрометиран. Доколкото ми е известно, не можете наистина да използвате NULL в низа, тъй като те се предават чрез аргументи execve. Така че дължината в крайна сметка е много голямо число, тъй като трябва да напишете някакво ненулево число, което да накара целия стек да избухне, същият е случаят с адреса за връщане. Пропускам ли нещо очевидно? Стрлен трябва ли да се експлоатира. Видях някои препратки към аритметично препълване на числа със знак, но не съм сигурен дали обръщането на локалните променливи прави нещо.

Кодът е публикуван по-долу и се връща към главна функция, която след това завършва програмата и работи на система с малък ред с изключена защита на стека, тъй като това е въвеждащо упражнение за infosec:

int TrickyOverflowSeq ( char *in )
{
    char       to_be_exploited[128];
    int        c;
    int        limit;

    limit = strlen(in);
    if (limit > 144)
        limit = 144;

    for (c = 0; c <= limit; c++)
            to_be_exploited[c] = in[c];

    return(0);
}

person Cal    schedule 28.01.2014    source източник
comment
Обърнете внимание, че стандартът C не казва нищо за относителните адреси на to_be_exploited, c или limit. Компилаторът е свободен да ги подреди в произволен ред; различни опции за компилатора могат да ги подредят в различен ред. Освен ако не използвате необичайна система, sizeof(int) == 4, така че 128 + 2 * 4 = 136, оставяйки ви поне 8 байта за презаписване на всяка контролна информация в стека (ако можете да презапишете c и limit; 16 байта, ако не можете ). Всичко, което трябва да направите, е да извикате програмата с аргумент от поне 144 знака: ./yourprog $(perl -e 'print "A" x 144').   -  person Jonathan Leffler    schedule 28.01.2014
comment
в контролирана среда (деактивиран ASLR) и приемете, че стекът расте надолу (от високо до ниско), можете да презапишете променливата за ограничение, така че да ви позволи да заобиколите ограничението и да копирате останалите данни в буфера. Проблемът е, че за да презапишете ограничението, ако приемете, че имате 32-битова подравнена машина, ще трябва да имате \x00 във вашия буфер/във, това няма да накара strlen да заобиколи len от 144, така че няма да можете да презапишете стойността. трябва да измисля начин да има \x00 в паметта, без всъщност да използва нулевия знак   -  person Rave    schedule 28.01.2014
comment
Да, това е 64-битова машина, но вашата идея е по същество решението (\x00 без използване на null)   -  person Cal    schedule 28.01.2014


Отговори (3)


Не знам откъде идва arg, но тъй като буферът ви е само 128 байта и ограничавате максималната дължина до 144, трябва да подадете само низ, по-дълъг от 128 байта, за да предизвикате препълване на буфера при копиране на in в to_be_exploited. Всеки злонамерен код ще бъде във входния буфер от позиции 129 до 144.

Дали това ще настрои правилно връщане на друго място зависи от много фактори.

person Ed S.    schedule 28.01.2014
comment
Съжалявам за това, беше печатна грешка. Ако предам низ ›128 байта, навлизам в територията на c и limit, но нищо след това, освен ако не променя limit. Мога да избера да прескоча c и да започна да модифицирам limit, но проблемът е, че модификацията на limit трябва да бъде прекратена с NULL, в противен случай се оказва много голямо число, причиняващо повреда на стека. Единственият начин, по който се сещам да направя това, е като се опитам да запазя указателите на рамката и да запазя адресите за връщане на предишните стекови рамки, които са дошли преди това. - person Cal; 28.01.2014
comment
Е, това, което преминавате, зависи от начина, по който стекът расте, но нека приемем, че расте, както казвате. Вие повреждате стека откъдето и да го погледнете; просто се опитваш да го направиш интелигентно. Единственото нещо, което трябва да бъде null прекратено, е in, защото в противен случай извиквате UB (твърде рано и неконтролируемо). Така че, ако in съдържа нулев прекратен низ от 145 байта, имате последните 8 байта за игра (това предполага 8 байта int). Можете да поставите там каквото искате и ако всичко е настроено, както казвате, ще презапишете limit, но само байт по байт в този цикъл. - person Ed S.; 28.01.2014

Упражнението обаче също така споменава, че е възможно да се използва само 1 вектор на аргумент за компрометиране на този код. Любопитен съм да видя как може да стане това.

...

Проблемът тук е, че дължината трябва да бъде презаписана, за да се осъществи препълването и адресът за връщане да бъде компрометиран.

Изглежда ми доста просто. Това магическо число 144 има смисъл, ако е sizeof(int) == 8, което би било, ако изграждате за 64 бита.

Така че, ако приемем оформление на стека, където to_be_exploited идва преди c и limit, можете просто да подадете много дълъг низ с боклуци в байтовете, започващи от отместване 136 (т.е. 128 + sizeof(int)), и след това внимателно изработени боклуци в байтовете, започващи с отместване 144. Това ще презапише limit, започвайки с този байт, като по този начин деактивира проверката на дължината. След това внимателно изработените боклуци презаписват адреса за връщане.

Можете да поставите почти всичко в 8-те байта, започвайки от отместване 136, и да ги накарате да направят число, което е достатъчно голямо, за да деактивира проверката за сигурност. Просто се уверете, че няма да получите отрицателно число. Например, низът "HAHAHAHA" би изчислил като цяло число 5206522089439316033. Това число е по-голямо от 144... всъщност е твърде голямо, тъй като искате тази функция да спре копирането, след като вашият низ бъде копиран. Така че просто трябва да разберете колко дълъг всъщност е вашият атакуващ низ и да поставите правилните байтове за тази дължина в тази позиция и атаката ще бъде копирана.

Имайте предвид, че нормалните функции за обработка на низове в C използват NUL байт като терминатор и спират копирането. Тази функция не прави това; то просто се доверява на limit. Така че можете да поставите всякакви боклуци, които искате, във входния низ, за ​​да използвате тази функция. Въпреки това, ако нормалните C библиотечни функции трябва да копират входните данни, може да се наложи да избягвате NUL байтове.

Разбира се, никой не трябва да въвежда този глупав код в производството.

ЕДИТ: Горното го написах набързо. Сега, когато имам повече време, прочетох отново въпроса ви и мисля, че по-добре разбирам какво искахте да обясните.

Чудите се как една струна може правилно да удря limit с правилна дължина, без strlen() да я отрязва накъсо. Това е невъзможно на big-endian компютър, но е напълно възможно на little-endian компютър.

На компютър с малък порядък първият байт е най-малкият байт. Вижте записа в Уикипедия:

http://en.wikipedia.org/wiki/Endianness

Всяко число, което не е абсурдно голямо, трябва да има нула в най-значимите си байтове. На компютър с порядъчен ред това означава, че първите няколко байта ще бъдат нула, ще действат като NUL и ще накарат strlen() да отреже низа, преди функцията да успее да унищожи limit. Въпреки това, на компютър с малък ред всички важни байтове, които искате да копирате, ще бъдат пред байтовете NUL.

В ранните дни на интернет беше обичайно компютрите с голям порядък (често закупени от Sun Microsystems) да изпълняват приложения за интернет сървъри. В наши дни най-разпространеният x86 сървърен хардуер е най-разпространен, а x86 е little-endian. На практика всеки, който внедри такъв използваем код като функцията TrickyOverflowSeq(), ще бъде придобит.

Ако не смятате, че този отговор е достатъчно изчерпателен, моля, публикувайте коментар, обясняващ коя част според вас трябва да покрия по-добре и аз ще актуализирам отговора.

person steveha    schedule 28.01.2014
comment
Разбирам какво казвате, но проблемът е намаляването на дължината до управляемо цяло число (около 152), което не може да бъде направено, освен ако няма нулеви терминатори във високите битове на дължината. Проблемът е, че низът за атака се копира от хост програма чрез execve и вярвам, че тези аргументи ще бъдат прочетени до този NULL терминатор, който не позволява четенето на експлойта след дължината, която ни е необходима, за да компрометираме адреса за връщане и презаписваме с адреса на буфера, за да започнем да изпълняваме нашия код. - person Cal; 29.01.2014
comment
Ако не можем да използваме /x00 в дълги високи байтове (дължината е 4 байта), тогава ще завършим с нещо подобно като най-малкото число за дължина /x01/x01/x01/x01 в Little Endian, което се оказва 0x01010101 = 16843009 - това е твърде голямо и определено ще взриви стека. Може би подхождам погрешно към това - person Cal; 29.01.2014
comment
Е, сега наистина навлизаме в дефинирано от изпълнението поведение. Ако execve() копира низовете, възможно е да постави низовете един до друг по такъв начин, че атаката да продължи да работи. Или не... Всъщност не съм атакувал компютри по този начин, просто се опитвам да разбера това. Както и да е, функцията, както е написана, е ужасно експлоатируема, но може би реалната й употреба случайно е защитена от execve(). Това всъщност не обезсилва точките, които вашите учители се опитват да ви впечатлят с това, но е възможно вашите учители да са пренебрегнали тази точка. - person steveha; 29.01.2014

Наясно съм, че това е доста стара публикация, но се натъкнах на въпроса ви, защото се озовах в същата ситуация с абсолютно същите въпроси като тези, които задавате в публикацията си и в коментарите.

Няколко минути по-късно реших проблема. Не знам колко от него трябва да "разваля" тук, тъй като AFAIK това е типичен проблем в много курсове по компютърна сигурност. Мога обаче да кажа, че решението наистина може да бъде постигнато с точно един аргумент... и с няколко променливи на средата. Допълнителен съвет: променливите на средата се съхраняват след аргументите на функцията в стека (както в на по-високи адреси от аргументите на функцията).

person jimkokko5    schedule 22.09.2019