Фундаментальные основы хакерства. Ищем операнды при взломе программ

Содержание статьи

  • Идентификация констант и смещений
  • Определение типа непосредственного операнда
  • Сложные случаи адресации или математические операции с указателями
  • Порядок индексов и указателей
  • Использование LEA для сложения констант
  • Заключение

Ког­да мы занима­емся ана­лизом лома­емой прог­раммы, пыта­ясь вос­ста­новить алго­ритм ее работы, нам нуж­но опре­делить типы опе­ран­дов ассем­блер­ных инс­трук­ций. Для это­го есть нес­коль­ко прос­тых пра­вил. Меж­ду тем сре­ди опе­ран­дов при­сутс­тву­ют кон­стан­ты и сме­щения, которые внеш­не очень похожи, но в то же вре­мя силь­но раз­лича­ются по спо­собам и целям вза­имо­дей­ствия. Поэто­му важ­но отде­лить одно от дру­гого, так как такие «игры» — один из глав­ных инс­тру­мен­тов раз­работ­чиков защит.

Фундаментальные основы хакерства

Пят­надцать лет назад эпи­чес­кий труд Кри­са Кас­пер­ски «Фун­дамен­таль­ные осно­вы хакерс­тва» был нас­толь­ной кни­гой каж­дого начина­юще­го иссле­дова­теля в области компь­ютер­ной безопас­ности. Одна­ко вре­мя идет, и зна­ния, опуб­ликован­ные Кри­сом, теря­ют акту­аль­ность. Редак­торы «Хакера» попыта­лись обно­вить этот объ­емный труд и перенес­ти его из вре­мен Windows 2000 и Visual Studio 6.0 во вре­мена Windows 10 и Visual Studio 2019.

Ссыл­ки на дру­гие статьи из это­го цик­ла ищи на стра­нице авто­ра.

 

Идентификация констант и смещений

Мик­ропро­цес­соры серии 80×86 под­держи­вают опе­ран­ды трех типов: регистр, непос­редс­твен­ное зна­чение, непос­редс­твен­ный ука­затель. Тип опе­ран­да явно зада­ется в спе­циаль­ном поле машин­ной инс­трук­ции, име­нуемом mod, поэто­му никаких проб­лем в иден­тифика­ции типов опе­ран­дов не воз­ника­ет. Регистр — ну, все мы зна­ем, как выг­лядят регис­тры; ука­затель по общепри­нято­му сог­лашению зак­люча­ется в квад­ратные скоб­ки, а непос­редс­твен­ное зна­чение записы­вает­ся без них. Нап­ример:

MOV ECX, EAX; ← регистровые операндыMOV ECX, 0x666; ← левый операнд регистровый, правый — непосредственныйMOV [0x401020], EAX; ← левый операнд — указатель, правый — регистр

Кро­ме это­го, мик­ропро­цес­соры серии 80×86 под­держи­вают два вида адре­сации памяти: не­пос­редс­твен­ную и кос­венную. Тип адре­сации опре­деля­ется типом ука­зате­ля. Если опе­ранд — непос­редс­твен­ный ука­затель, то и адре­сация непос­редс­твен­на. Если же опе­ранд‑ука­затель — регистр, то такая адре­сация называ­ется кос­венной. Нап­ример:

MOV ECX,[0x401020] ← непосредственная адресацияMOV ECX, [EAX] ← косвенная адресация

Для ини­циали­зации регис­тро­вого ука­зате­ля раз­работ­чики мик­ропро­цес­сора вве­ли спе­циаль­ную коман­ду, вычис­ляющую зна­чение адресно­го выраже­ния addr и прис­ваивающую его регис­тру REG, — LEA REG, [addr]. Нап­ример:

LEA EAX, [0x401020] ; Регистру EAX присваивается значение указателя 0x401020MOV ECX, [EAX] ; Косвенная адресация — загрузка в ECX двойного слова, ; расположенного по смещению 0x401020

Пра­вый опе­ранд коман­ды LEA всег­да пред­став­ляет собой ближ­ний (near) ука­затель (исклю­чение сос­тавля­ют слу­чаи исполь­зования LEA для сло­жения кон­стант — под­робнее об этом см. в одно­имен­ном пун­кте). И все было бы хорошо… да вот, ока­зыва­ется, внут­реннее пред­став­ление ближ­него ука­зате­ля экви­вален­тно кон­стан­те того же зна­чения. Отсю­да LEA EAX, [0x401020] рав­носиль­но MOV EAX, 0x401020. В силу опре­делен­ных при­чин MOV зна­читель­но обог­нал в популяр­ности LEA, прак­тичес­ки вытес­нив пос­леднюю инс­трук­цию из упот­ребле­ния.

От­каз от LEA породил фун­дамен­таль­ную проб­лему ассем­бли­рова­ния — проб­лему OFFSET’a. В общих чер­тах ее суть зак­люча­ется в син­такси­чес­кой нераз­личимос­ти кон­стант и сме­щений (ближ­них ука­зате­лей). Конс­трук­ция MOV EAX, 0x401020 может гру­зить в EAX и кон­стан­ту, рав­ную 0x401020 (при­мер соот­ветс­тву­юще­го C-кода: a=0x401020), и ука­затель на ячей­ку памяти, рас­положен­ную по сме­щению 0x401020 (при­мер соот­ветс­тву­юще­го C-кода: a=&x). Сог­ласись, a=0x401020 сов­сем не одно и то же, что a=&x! А теперь пред­ставь, что про­изой­дет, если в пов­торно ассем­бли­рован­ной прог­рамме перемен­ная х ока­жет­ся рас­положе­на по ино­му сме­щению, а не 0x401020? Пра­виль­но — прог­рамма рух­нет, ибо ука­затель a по‑преж­нему ука­зыва­ет на ячей­ку памяти 0x401020, но здесь теперь «про­жива­ет» сов­сем дру­гая перемен­ная!

По­чему перемен­ная может изме­нить свое сме­щение? Основных при­чин тому две. Во‑пер­вых, язык ассем­бле­ра неод­нозна­чен и допус­кает дво­якую интер­пре­тацию. Нап­ример, конс­трук­ции ADD EAX, 0x66 соот­ветс­тву­ют две машин­ные инс­трук­ции: 83 C0 66 и 05 66 00 00 00 дли­ной три и пять байт соот­ветс­твен­но. Тран­сля­тор может выб­рать любую из них, и не факт, что ту же самую, которая была в исходной прог­рамме (до дизас­сем­бли­рова­ния). Невер­но «уга­дан­ный» раз­мер вызовет сме­щение всех осталь­ных инс­трук­ций, а вмес­те с ними и дан­ных. Во‑вто­рых, сме­щение не замед­лит выз­вать модифи­кацию прог­раммы (разуме­ется, речь идет не о замене JZ на JNZ, а о нас­тоящей адап­тации или модер­низации), и все ука­зате­ли тут же «посып­лются».

Вер­нуть работос­пособ­ность прог­раммы помога­ет дирек­тива offset. Если MOV EAX, 0x401020 дей­стви­тель­но заг­ружа­ет в EAX ука­затель, а не кон­стан­ту, по сме­щению 0x401020 сле­дует соз­дать мет­ку, име­нуемую, ска­жем, loc_401020. Так­же нуж­но MOV EAX, 0x401020 заменить на MOV EAX, offset loc_401020. Теперь ука­затель EAX свя­зан не с фик­сирован­ным сме­щени­ем, а с мет­кой!

А что про­изой­дет, если пред­варить дирек­тивой offset кон­стан­ту, оши­боч­но при­няв ее за ука­затель? Прог­рамма отка­жет или ста­нет работать некор­рек­тно. Допус­тим, чис­ло 0x401020 выража­ло собой объ­ем бас­сей­на, в который вода вте­кает через одну тру­бу, а вытека­ет через дру­гую. Если заменить кон­стан­ту ука­зате­лем, то объ­ем бас­сей­на ста­нет равен… сме­щению мет­ки в заново ассем­бли­рован­ной прог­рамме и все рас­четы полетят к чер­ту.

Та­ким обра­зом, очень важ­но опре­делить типы всех непос­редс­твен­ных опе­ран­дов, и еще важ­нее опре­делить их пра­виль­но. Одна ошиб­ка может сто­ить прог­рамме жиз­ни (в смыс­ле работос­пособ­ности), а в типич­ной прог­рамме тысячи и десят­ки тысяч опе­ран­дов!

От­сюда воз­ника­ет два воп­роса:

  • Как вооб­ще опре­деля­ют типы опе­ран­дов?
  • Мож­но ли их опре­делять авто­мати­чес­ки (или на худой конец хотя бы полу­авто­мати­чес­ки)?

Ти­пы опе­ран­дов

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *