Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
Скачать 2.5 Mb.
|
18 0x200 Программирование Новая функция обратит на себя внимание рынка, а оптимизацию по скорости и памяти большинство пользователей не заметят. Когда все определяется в конечном счете деньгами, тратить время на искусные хаки для оптимизации просто нет смысла. По настоящему оценить элегантность программы могут только хаке- ры – компьютерные энтузиасты, конечная цель которых не прибыль, а желание выжать из своего старенького Commodore 64 все, на что он способен; создатели эксплойтов, которые пишут изумительные кро- хотные фрагменты кода, способные проскользнуть сквозь узкую щел- ку в системе защиты; все, кого увлекает поиск лучшего из возмож- ных решений. Это те, кто действительно любит программировать, по- настоящему ценя красоту элегантного кода и остроумие изобретатель- ного хака. Чтобы применять эксплойты, необходимо освоить програм- мирование, поэтому последнее послужит для нас естественной отправ- ной точкой. 0x210 Что такое программирование? Программирование – вполне естественное и интуитивное понятие. Программа представляет собой лишь ряд предложений, написанных на особом языке. С программами мы встречаемся повсеместно, и даже технофобы ежедневно имеют с ними дело. Схемы проезда, кулинар- ные рецепты, футбольные матчи и ДНК – все это программы. Типич- ная «программа» для шофера может выглядеть так: Начать движение по Главной улице в восточном направлении. Двигаться по Главной улице до церкви на правой стороне. Если движение перекрыто из-за ремонта, повернуть направо на 15-ю улицу, затем повернуть налево на Сосновую улицу, затем повернуть направо на 16-ю улицу. В противном случае продолжить движение до 16-й улицы и повернуть направо. Продолжать движение по 16-й улице, затем повернуть налево на Дорогу к цели. Проехать по Дороге к цели 5 км, нужный дом будет по правой стороне. Адрес: Дорога к цели, дом 743. Каждый, кто понимает человеческий язык, способен понять и выпол- нить эти указания. Правда, они лаконичны, но каждая инструкция по- нятна, по крайней мере тем, кто умеет читать. Но компьютер не понимает обычную речь, а только машинный язык. Чтобы заставить компьютер что-то сделать, надо написать ему ин- струкции на его языке. Однако машинный язык выглядит непонятно, и с ним трудно работать. Он состоит только из битов и байтов и специ- фичен для каждой машинной архитектуры. Чтобы написать програм- му на машинном языке, скажем, для процессора Intel x86, нужно знать числовое значение, соответствующее каждой его команде, осо- бенности ее выполнения и немыслимое количество прочих деталей, от- носящихся к программированию низкого уровня. Такое программиро- вание – долгий, трудоемкий и уж явно не интуитивный процесс. 0x220 Псевдокод 19 Преодолеть сложность написания программ на машинном языке позво- ляет транслятор. Одним из видов трансляторов в машинный язык яв- ляется ассемблер – программа, которая транслирует текст на языке ас- семблера в код, понятный машине. Язык ассемблера не так загадочен, как машинный язык, поскольку различные команды и переменные в нем записываются при помощи имен, а не чисел. Однако и язык ас- семблера не очень-то нагляден. Названия команд понятны лишь посвя- щенным, а язык остается специфичным для данной архитектуры. Это означает, что так же как машинный язык для процессоров Intel x86 от- личается от машинного языка для процессоров Sparc, так и язык ассем- блера для x86 отличается от языка ассемблера для Sparc. Любая про- грамма, написанная на языке ассемблера для процессора одной архи- тектуры, не сможет работать на процессоре другой архитектуры. Если программа написана на языке ассемблера x86, для выполнения в архи- тектуре Sparc ее придется переписать. Кроме того, чтобы писать эффек- тивные программы на языке ассемблера, необходимо знать многие под- робности архитектуры этого процессора. Эти проблемы становятся менее существенными, если применяется дру- гой вид транслятора, называемый компилятором. Компилятор преоб- разует язык высокого уровня в машинный язык. Языки высокого уров- ня гораздо нагляднее языков ассемблера и могут преобразовываться в машинные языки процессоров различных архитектур. Это означа- ет, что на языке высокого уровня программу можно написать один раз, а компилятор преобразует этот программный код в машинный язык конкретной архитектуры. Примеры языков высокого уровня – C, C++ и FORTRAN. Написанная на языке высокого уровня программа зна- чительно легче читается и больше похожа на естественный язык, чем язык ассемблера или машинный язык, но тем не менее должна придер- живаться очень жестких правил написания команд, иначе компилятор не сможет их понять. 0x220 Псевдокод У программистов есть еще один вид языка программирования, назы- ваемый псевдокодом. Псевдокод – это естественный язык, по структуре похожий на язык высокого уровня. Его не понимают компиляторы, ас- семблеры и какие-либо компьютеры, но он помогает программистам ор- ганизовать инструкции. Псевдокод не имеет четкого определения. Раз- ные люди пишут на псевдокоде немного по-разному. В некотором роде, это туманное отсутствующее звено между естественными языками, на- пример английским, и языками программирования высокого уровня вроде C. Псевдокод помогает понять общие стандартные принципы про- граммирования. 20 0x200 Программирование 0x230 Управляющие структуры Без управляющих структур программа представляла бы собой просто ряд последовательно выполняемых инструкций, или команд. Для про- стейших программ это приемлемо, но большинство программ, в частно- сти наш пример схемы проезда, оказываются сложнее. В них есть такие инструкции, как «если движение перекрыто из-за ремонта» и «в про- тивном случае». Эти инструкции называют управляющими структу- рами: они изменяют порядок выполнения программы с простого после- довательного на более сложный и полезный. 0x231 If-then-else В нашем примере движения по городу Главная дорога может оказать- ся закрытой на ремонт. Такую ситуацию нужно обработать с помощью специальных инструкций. В противном случае выполняется первона- чальный набор инструкций. Учесть в программе такие особые случаи позволяет одна из наиболее естественных управляющих структур if- then-else (если-то-иначе). Ее общий вид: If (условие) then { Набор инструкций, выполняемых при соблюдении условия; } Else { Набор инструкций, выполняемых при несоблюдении условия; } В этой книге используется псевдокод, похожий на C, поэтому каж- дая инструкция оканчивается точкой с запятой, а наборы инструкций определяются с помощью фигурных скобок и отступов. Если приведен- ную выше схему проезда преобразовать в псевдокод, получится при- мерно следующее: Двигаться по Главной улице; If (движение перекрыто) { Повернуть направо на 15-ю улицу; Повернуть налево на Сосновую улицу; Повернуть направо на 16-ю улицу; } Else { Повернуть направо на 16-ю улицу; } Каждая инструкция занимает отдельную строку, а наборы инструкций, выполняющихся при определенном условии, объединяются фигур- ными скобками и для большей наглядности размещаются с отступом. 0x230 Управляющие структуры 21 В C и многих других языках программирования ключевое слово then подразумевается по умолчанию, поэтому в приведенном псевдокоде оно опущено. Конечно, есть много языков, синтаксис которых требует слова then, на- пример BASIC, Fortran и даже Pascal. Такие синтаксические различия между языками очень поверхностны, базовая структура остается той же. Понимающий основные концепции этих языков программист до- статочно легко освоит синтаксические различия между ними. Далее в этой книге используется язык C, поэтому и псевдокод здесь имеет аналогичный синтаксис, хотя, вообще говоря, видов псевдокода много. Другое стандартное правило синтаксиса C состоит в том, что если набор инструкций в фигурных скобках состоит всего из одной инструкции, фигурные скобки можно опустить. Для наглядности кода полезно пи- сать эти инструкции с отступом, но синтаксически это необязательно. Приведенную выше схему проезда можно переписать так: Двигаться по Главной улице; If (движение перекрыто) { Повернуть направо на 15-ю улицу; Повернуть налево на Сосновую улицу; Повернуть направо на 16-ю улицу; } Else Повернуть направо на 16-ю улицу; Это правило действует для всех управляющих структур, встречающих- ся в книге, и оно само может быть записано с помощью псевдокода. If (набор инструкций состоит всего из одной команды) Использовать фигурные скобки, служащие для объединения инструкций, необязательно; Else { Использование фигурных скобок обязательно; Поскольку необходим логический способ объединения инструкций; } Даже само описание синтаксиса можно рассматривать как простую программу. Существуют разновидности конструкции if-then-else, на- пример операторы select/case, но логический принцип сохраняется: если случится это, то надо действовать так, иначе следует выполнять другие действия (которые могут включать новые конструкции if-then). 22 0x200 Программирование 0x232 Циклы while/until Другое элементарное понятие программирования – конструкция while (пока), разновидность цикла. Часто программисту требуется выпол- нить некоторую группу инструкций несколько раз. Подобную задачу программа решает с помощью цикла, но ей необходим набор условий, по которым она определит, что цикл нужно завершить, чтобы не выпол- нять его бесконечно. Цикл while указывает на то, что заданную груп- пу инструкций нужно выполнять, пока выполняется заданное условие. Вот пример простой программы для голодной мыши: while (голодна) { ищи что-то съедобное; съешь то, что нашла; } Набор из двух инструкций, следующий за оператором while, будет по- вторно выполняться, пока мышь все еще голодна. Каждый раз мышь может найти разное количество еды – от крошки хлеба до целой бухан- ки. Поэтому то, сколько раз будет выполнен набор инструкций в этом операторе while, зависит от того, сколько пищи найдет мышь. Другой вариант цикла – оператор until (пока не), имеющийся, напри- мер в языке программирования Perl (в C этот синтаксис не применя- ется). Цикл until – это тот же while, в котором условие имеет обратное значение. Для той же мыши программа с циклом until будет выглядеть так: until (наелась) { ищи что-то съедобное; съешь то, что нашла; } Естественно, любой оператор вроде until можно превратить в цикл while . Выше в схеме проезда была инструкция Двигаться по Главной улице до церкви на правой стороне . Ее можно заменить стандартным циклом while , изменив условие на противоположное. while (справа нет церкви) Двигаться по Главной улице; 0x233 Циклы for Еще один вариант цикла – цикл for. Он удобен в случае, когда програм- мисту нужно, чтобы цикл выполнился заданное число раз. В виде цик- ла for можно записать указание проехать по Дороге к цели 5 км: for (5 раз) Проехать 1 км; 0x240 Основные понятия программирования 23 В действительности цикл for – тот же while со счетчиком. Эту же ин- струкцию можно записать так: Установить счетчик в 0; while (счетчик меньше 5) { Проехать 1 км; Увеличить счетчик на 1; } Синтаксис цикла for на псевдокоде C еще нагляднее: for (i=0; i<5; i++) Проехать 1 км; В данном случае имя счетчика i, а оператор for разбит на три части, раз- деленные точкой с запятой. В первой части объявляется счетчик, и его значение устанавливается равным 0. Вторая часть – это как цикл while со счетчиком: выполнять цикл, пока счетчик соответствует этому усло- вию. Завершающая третья часть описывает действия, совершаемые со счетчиком при каждом проходе цикла. В данном случае i++ кратко со- общает, что к счетчику с именем i нужно добавить 1. С использованием всех управляющих конструкций схему проезда из начала главы 0х210 можно записать в виде псевдокода: Начать движение по Главной улице в восточном направлении; while (справа нет церкви) Двигаться по Главной улице; if (движение перекрыто) { Повернуть направо на 15-ю улицу; Повернуть налево на Сосновую улицу; Повернуть направо на 16-ю улицу; } else Повернуть направо на 16-ю улицу; Повернуть налево на Дорогу к цели; for (i=0; i<5; i++) Проехать 1 км; Остановиться у дома 743 по Дороге к цели; 0x240 Основные понятия программирования В следующих разделах мы познакомимся с другими общими поняти- ями программирования. Эти понятия используются во многих язы- ках программирования с некоторыми синтаксическими различиями. По ходу представления этих понятий я буду иллюстрировать их приме- рами псевдокода с C-образным синтаксисом. К концу псевдокод станет очень похож на C. 24 0x200 Программирование 0x241 Переменные Счетчик, используемый в цикле for, фактически является переменной. Переменную можно представить как некоторый объект, содержащий данные, которые могут изменяться, – отсюда и название. Бывают так- же переменные, которые не меняются, поэтому их назвали константа- ми. Если вернуться к примеру с ездой, то скорость машины – перемен- ная, а ее цвет – константа. В псевдокоде переменные абстрактны, но в C (и многих других языках), прежде чем использовать переменную, нуж- но объявить ее, присвоив ей определенный тип. Это связано с тем, что программа на C в конечном счете компилируется в выполняемую про- грамму. Подобно тому как в кулинарном рецепте сначала перечисля- ются все необходимые ингредиенты, объявления переменных позво- ляют выполнить некоторые приготовления перед тем, как приступить к основной программе. Все переменные будут храниться где-то в памя- ти, и их объявление дает компилятору возможность более эффективно распределить эту память. Хотя в конечном счете как бы вы ни объяви- ли переменную, это всего лишь участок памяти. В языке C переменной присваивается тип, описывающий информацию, которую должна хранить эта переменная. Самые распространенные типы: int (целые числа), float (десятичные числа с плавающей точкой 1 ) и char (символьные значения из одного символа). Переменные описыва- ются просто – с помощью ключевого слова, после которого идет список переменных, например: int a, b; float k; char z; Переменные a и b теперь определены как целые, k может принимать значения в виде чисел с плавающей точкой (например 3,14), а z содер- жит символьное значение, например A или w. Переменным можно при- сваивать значения во время объявления или в любой последующий мо- мент с помощью оператора =. int a = 13, b; float k; char z = ‘A’; k = 3.14; z = ‘w’; b = a + 5; После выполнения этих инструкций переменные будут содержать сле- дующие значения: a – число 13, k – число 3,14, z – букву w, а b – чис- ло 18, поскольку 13 + 5 = 18. Переменные просто позволяют запоминать 1 В России целую и дробную части числа принято разделять запятой, поэто- му применяется термин «плавающая запятая». – Прим. ред. 0x240 Основные понятия программирования 25 значения; однако в C вы сначала обязаны объявить тип каждой пере- менной. 0x242 Арифметические операторы Выражение b = a + 7 – пример простейшего арифметического операто- ра. Приведем символы, используемые в C для разных арифметических операций. Первые четыре операции имеют знакомый вид. Деление по модулю (це- лочисленное деление) может показаться новинкой, но на самом деле это всего лишь остаток от деления. Если a равно 13, то поделив 13 на 5, по- лучим 2 и 3 в остатке, откуда следует, что a % 5 = 3. Кроме того, посколь- ку переменные a и b – целые, оператор b = a / 5 записывает в b значе- ние 2, поскольку это целая часть результата. Чтобы получить более кор- ректный результат 2,6, нужно использовать переменные с плавающей точкой. Операция Символ Пример Сложение + b = a + 5 Вычитание - b = a - 5 Умножение * b = a * 5 Деление / b = a / 5 Деление по модулю % b = a % 5 Чтобы заставить программу применять эти понятия, нужно говорить на понятном ей языке. В языке C есть и несколько способов сокращен- ной записи этих арифметических операций. Об одной из них, исполь- зуемой преимущественно в циклах, уже говорилось. Полная запись Сокращенная запись Описание i = i + 1 i++ или ++i Прибавить 1 к переменной i = i – 1 i-- или --i Вычесть 1 из переменной Эти сокращенные выражения можно объединить с другими арифмети- ческими операциями в более сложное выражение. Тут-то и выясняется разница между i++ и ++i. Первое выражение означает, что нужно уве- личить значение i на 1 после того, как будет выполнена арифметиче- ская операция, в второе предписывает увеличить i на 1 перед выполне- нием арифметической операции. Поясним это на следующем примере. 26 0x200 Программирование int a, b; a = 5; b = a++ * 6; После выполнения этих инструкций переменная b будет содержать зна- чение 30, а переменная а – значение 6, потому что сокращенная запись b = a++ * 6; равносильна следующим инструкциям: b = a * 6; a = a + 1; Однако при использовании команды b = ++a * 6; порядок сложения с a меняется, что равносильно следующим инструкциям: a = a + 1; b = a * 6; Поскольку порядок действий изменился, b теперь будет содержать 36, но a по-прежнему будет хранить 6. Нередко в программе требуется изменить переменную прямо на месте. Например, нужно прибавить к переменной какое-то значение, ска- жем 12, и записать результат в ту же переменную (например, i = i + 12). Для этой популярной операции есть специальная сокращенная запись. Полная запись Сокращенная запись Описание i = i + 12 i+=12 Прибавить к переменной некоторое значение i = i - 12 i-=12 Вычесть из переменной некоторое значение i = i * 12 i*=12 Умножить переменную на некоторое значение i = i / 12 i/=12 Разделить переменную на некоторое значение |