Информационная безопасность

       

Листинги и комментарии


Поскольку эта программка для меня что-то вроде хобби, к которому я периодически возвращаюсь, то за последние два года она выросла до 24-х проходов с весьма хитрыми "исподвывертами", длящимися несколько минут даже на моем неслабом компьютере. Но поскольку в данном случае нам важно всего лишь понять смысл программы, а не собрать компилируемый код, то для начала достаточно нескольких проходов, которые, скажем, позволят уменьшить размер файла в 10 раз.

Проход 05_stringer.pl - сборка строк:

01 if (m|db\s+..h\s+;\s+([0-9A-Za-z\@:\.\?/\-])|) { 02 $buff=(defined ($buff) && length ($buff)>0?"$buff$1":$1); 03} else { 04 if (length ($buff)) { 05 print (m/db\s+0/)?" db '$buff', 0\n":" db '$buff'\n$_"; 06 $buff=""; 07} else { 08 print; 09} 10}

Комментарий: IDA хорошо собирает строковые литералы и даже дает им осмысленные имена, но только в случае, если на строку есть ссылка. К сожалению, если этих ссылок нет, то строка так и остается "толпой одиноких байт". Приведенный выше код исправляет ситуацию: если встречается строка типа db 65h; A - мы начинаем накапливать строку в переменной $buff. Все последующие символы добавляются в строку, до тех пор пока не встретим что-то другое. "Что-то другое" может быть db 0 или нет - в первом случае мы предполагаем, что строка "закрывается" этим символом, и присоединяем его к строке. В противном случае наша программа выводит строку, за которой идет то, что распознано нами как не-строка (поз 05). Поскольку в следующем проходе мы будем "складывать" повторяющиеся db 0 в один dup, то удобнее будет заранее "сложить" строки, чтобы потом не приходилось выискивать db 0 в dup'ах. Это та причина, по которой данный проход имеет номер 05 - он был добавлен сюда позже.

Проход 10_squezee.pl, упаковка

01 if (!m/^\s*(;|$)/ &&!m/(align|assume|model|segment|p386n|public)/) { 02 s/\s+/ /g; 03 if (m/unexplored/) { 04 $unxcnt=($unxcnt?$unxcnt+1:1); 05} elsif (m/db 0/) { 06 $zeros=($zeros?$zeros+1:1); 07} else { 08 if ($unxcnt) {print " db $unxcnt dup (?)\n"; $unxcnt=0;} 09 elsif ($zeros) {print $zeros==1?" db 0\n":" db $zeros dup (0)\n"; $zeros=0;} 10 s/;....
XREF:.*$//; 11 s/0(FFFF)?FFFFh/-1/; 12 print $_,"\n"; 13} 14}

Комментарий: директивы в строке 01 имеют смысл только для ассемблера, для нас же они - мусор на 100%, так что просто игнорируем их. Строка 02 заменяет все "blank spaces" (то есть последовательности пробелов, табуляций и прочих "невидимок") одним пробелом. Возможно, это уменьшает читабельность, но она нам пока ни к чему. Следующий блок "скатывает" последовательности db 0 и db?. Первое вхождение такой строки начинает отсчет, и вся последовательность превращается в одну строку типа db 100 dup (?). Слово unexplored ничего нам не дает, так как? именно это и значит. Строка 10 "убивает" все перекрестные ссылки - поскольку у нас уже нет адресов в левой части листинга, то они тоже мало что дают. В строке 12 добавляется перевод строки к строкам, которые мы пропускаем без обработки, поскольку он тоже blank, и поэтому был удален в строке 02.

Проход 20_subrouter.pl, обработка определений функций:

01 if (!m/(mov esp, ebp|push ebp|mov ebp, esp|pop ebp|push e [sd] i|pop e [sd] i)/&&!m/^(var|arg)/) { 02 if (m/ proc /) { 03 s/^(.+) proc.*$/\n$1 {/; 04} 05 if ($retn) { 06 if (m/endp/) { 07 s|^(.+) endp|}|; 08} else {print "}\n";} 09} 10 $retn=m/retn/; 11 if (!$retn) {print;} 12}

Комментарий: в первой же строке удаляются все стандартные прологи и эпилоги функций. Вы можете представить ситуацию, когда эти команды используются в другом контексте, но в нашем случае компилятор этого не делает - поэтому тут все "чисто". Тут же я удаляю все определения аргументов и переменных - они только мешают (можете считать, что в нашем С все переменные определяются неявно в момент использования). Определения функций заменяются на С-подобные в строке 03. Небольшие трюки связаны с обработкой конца функции - правильно написанная функция всегда имеет одну точку выхода, так что retn - наверняка конец функции, однако IDA в этом не уверен.

Проход 30_caller.pl, обработка вызовов функций:



01 if (!m/add esp/) { 02 s/^ call (.+); (.+)$/ $3/; 03 s/ds://; 04 s/^ call (.+) / $1/; 05 print; 06}

Комментарий: первая строка отфильтровывает строки типа add esp, 10h - компилятор использует их только для коррекции после вызова функций из стандартных библиотек типа libc - и никак иначе. Команды call заменяются на комментарии от IDA в случае "хорошо известных" вызовов (эти комментарии нарочно не удалялись) или на имена функций нашей программы, если IDA нам ничего не подсказывает. В более полном варианте в этом же проходе можно "скатать" стек. При этом учитывается, какие переменные были помещены в стек перед вызовом, и вместо пачки push аргументы перечисляются в скобках.

Проход 40_reasm.pl, замена ассемблерных конструкций:

s/\[ebp\+(.+)\]/$1/; s/\]//; s/\[//; s/mov (.+), (.+)/$1=$2/; s/lea (.+), (.+)/$1=&$2/; s/xor (.+), $1/$1\=0/; s/sub (.+), (.+)/$1-=$2/; s/add (.+), (.+)/$1+=$2/; s/offset /*/; s/ptr //; s/short //; s/unknown_libname/lib/; print;

Эти строки делают несколько замен общего характера, отличительная особенность которых заключается в том, что после этого прохода мы больше не имеем дело с кодом ассемблера.

В этом месте мы уже достигли определенного прогресса: текст СБ.asm уменьшился ровно в 14 раз (с 28 Мб до 2 Мб), работать с ним стало приятно и полезно. Не хочу утомлять вас более сложным кодом - сейчас главное понять, как это делается, и почувствовать вкус к такого рода "улучшению мира".


Содержание раздела