Досвід дизассемблирования великий
Тоді, рік тому, я спробував аналізувати код простеньку програму і був страшенно здивований тим, що дизассемблер робить це неправильно, і при повторному ассеблірованіі програма не працювала так, як треба. Тоді ж мені вдалося поговорити із знавцем і, хоча я відчував себе наївним, мені вдалося з'ясувати головне: ПОВНЕ, АВТОМАТИЧНЕ дизассемблирования НЕМОЖЛИВО, над тим текстом, який видає дизассемблер, потрібно досить довго працювати, перш ніж розбирати цього тексту дасть працездатну програму.
Надалі я постараюся розповісти про тих прийомах, які перетворюють "поганий" текст в "хороший". тобто в текст, який не тільки дає коректно працюючу програму при асемблюванні, але і дозволяє себе змінити, щоб удосконалити вихідну програму.
ЧОМУ DisDoc?
SOURSER - це назва знають всі, хто хоча б краєм вуха чув про дізассеблірованіі. Вважається, що це дізассеблер чудовий, потужний, що не має конкурентів. Я думаю, що чутки про величезні переваги SOURSERа силь але перебільшені. У мене склалося таке враження, що при дізассемблірова ванні невеликих програм (до 7 кб.) SOURSER краще. Коли програма велика (в моєму випадку - 58 кб), SOURSER працює дуже повільно і, на мій погляд, не дає ніяких переваг.
Вибір дизассемблера DisDoc 2.3 був для мене багато в чому випадковий. Починаючи роботу, я отримав тексти на асемблері як за допомогою SOURSERa (версія 3.07), так і за допомогою дизассемблера DisDoc 2.3. Потім обидва тексти після усунення очевидних помилок були ассембліровани. І ось, то, що було видано SOURSERом, повисло відразу, а то, що видав DisDoc 2.3, перш ніж повиснути, вивело на екран кілька ліній. Це і визначило вибір. В процесі роботи я не раз мав можливість оцінити основна перевага дизассемблера DisDoc - інтуїтивно зрозумілий, неізощренний, зручний і компактний лістинг.
Щоб зрозуміти подальший, необхідно познайомитися з уривком з лістингу, який видає DisDoc 2.3
У будь-якому випадку обов'язково зустрінуться
фундаментальні проблеми
1. Проблема OFFSETa
Припустимо, що в тексті, який видав дізаccемблер є такий фрагмент:
Можливо, тут у багатьох виникне сумнів - чи потрібно замінювати число на відповідний OFFSET - адже, здавалося б, в заново ассемблірованной програмі дані будуть мати той же зміщення? На жаль, це не так. По-перше, ми, як правило, не знаємо, який асемблер застосовувався при транслювання оригінального тексту, а коди, отримані за допомогою різних ассемблеров матимуть різну довжину, що призведе до зміни зсувів. Наприклад, команда AND CX, 0007h транслюється MASMом 5.1 і TASMом 1.01 як 83E107 і займає 3 байтa. Але ця ж команда може бути трансльований як 81E10700 і займати 4 байти. По-друге, навіть якщо зміщення збережеться, програма не піддасться модифікації, так як при вставці будь-якого фрагмента коду зміняться зміщення і все "розвалиться". Отже, OFFSETи дозволяють склеїти програму, роблять її придатною для модифікації. Розібраний приклад досить примітивний. Спробуємо розглянути більш складні ситуації і насамперед досліджуємо фрагмент тексту, виданий дизассемблером:
Розглянемо ще один приклад непрямого виклику підпрограми, в якому OFFSET потрапляє в область даних.
Щоб з'ясувати, що являє собою 8792h, потрібно подивитися в область із зсувами, близькими до цього числа. Наведемо відповідний фрагмент, виданий дизассемблером:
Видно, що зміщення 08792 відповідає слово 0d5,93. Тепер залишається зауважити, що зі зміщення 093d5 у вихідній програмі починається фрагмент який висів коду
Отже, весь розібраний приклад - це хитромудрий непрямий виклик підпрограми. Виправлений фрагмент повинен виглядати так:
Тут я передбачаю великі заперечення. Мені скажуть, що все це можна інтерпретувати інакше, що мої докази непереконливі і т.д. З цим я абсолютно згоден. Більш того, ці докази непереконливі і для мене. Набагато сильніше переконує те, що програма після ассемблирования працює! Дизасемблювання, як і налагодження програм - процес інтуїтивний. Досвідчена людина відчуває особливе задоволення від того, що його невмотивовані здогади згодом підтверджуються. Як часто думка, яка прийшла в автобусі, уві сні, в компанії, в самій невідповідної обстановці - виявляється вірною! Завершимо цей пункт ще одним досить хитрим прикладом. У тексті, який видав дизассемблер, зустрівся такий фрагмент:
Виникає все те ж питання - що таке 4f71h - число або зміщення? Щоб відповісти на це питання, потрібно зрозуміти, що робить цю ділянку програми. Давайте спробуємо в цьому розібратися. Очевидно, з стека виштовхується число, порівнюється з 4f71h і якщо немає рівності, виштовхується наступне число. Якщо число дорівнює 4f71h, то воно знову заталкивается в стек і відбувається повернення з підпрограми. Але куди? Ясно, що в те місце, зміщення якого було у вихідній програмі одно 4f71h. Як видно з тексту, в цьому місці стояв виклик підпрограми s229. Значить, таким дивним чином викликається підпрограма і 4f71h - це зміщення! Виправлений фрагмент повинен виглядати так:
2. Як відрізнити дані від команд?
Будь дизассемблер плутає дані і команди. Особливо це відноситься до .COM програмами, де все перемішано. Розглянемо простий приклад:
У цьому фрагменті зустрілися дві вигадливих, який висів інструкції:
Зверху вони обмежені інструкцією повернення з підпрограми ret, а знизу - міткою m03e5c. Ясно, що ці інструкції можуть бути тільки даними. Після переробки наведений фрагмент повинен виглядати так:
Тут виникає ще одне питання: чому в одному випадку варто dw, а в іншому - db? Відповідь міститься в тексті, який видав дизассемблер. Там можна знайти такі інструкції:
Звідки випливає, що d03e58 розглядається як слово, а d03e5a - як байт. Розглянемо трохи складніший, але, тим не менш, дуже характерний приклад.
У наведеному фрагменті тексту мітка b03f6b відсутня. Тим часом ця мітка повинна "розрубати" навпіл інструкцію add BYTE PTR [si], bh. яка починається в оригінальній програмі, яку піддають дизассемблирования, зі зміщення 03f6a. Вихід тут може бути тільки один - зміщення 03f6a відповідає байт даних, а інструкція починається зі зсуву 03f6b. Виправлений фрагмент повинен виглядати так:
Плутанина між даними і інструкціями виникає досить часто. SOURSER здатний видавати цілі метри безглуздих інструкцій. DisDoc 2.3 в цьому відношенні поводиться краще.
3. Залежність від транслятора
Програмісти на асемблері схильні нехтувати правилами хорошого тону, порушувати всі мислимі табу, і це створює додаткові труднощі при дизассемблирования. Як приклад наведемо фрагмент коду, виданого дизассемблером
Цей фрагмент можна вважати цілком безневинним, і дійсно, він дизасемблювати правильно. Вся біда в тому, що програміст задумав змінювати цей фрагмент, тобто різати по живому. Виявляється, в програмі є ще такий шматок
Далі слід зрозуміти, що роблять інструкції, наведені на рис.1 з підпрограмою s25. Нехай ця підпрограма асслемблірована за допомогою TASM 1.01. Виданий ассемблером код буде таким, як показано на малюнку 2.
Але вся біда в тому, що вихідна програма була ассемблірована іншим ассемблером і має вигляд, показаний на малюнку 3. Як видно з порівняння малюнків 2 і 3, TASM 1.01 і невідомий асемблер транслюють інструкції ADD по-різному, і це призводить до катастрофічних наслідків. Дійсно, подивимося, як впливає ділянку коду, показаний на Рис.1 (перед цим замінимо 086bh на OFFSET d0086b) на підпрограму s25, що транслюється TASMом (рис.4) і невідомим ассемблером (рис.5).
Порівняння малюнків 4 і 5 показує, що логіка роботи програми змінюється залежно від того, який асемблер застосовувався. Як викрутитися з цієї ситуації, якщо потрібного асемблера немає під рукою? Найпростіший, але не дуже гарний шлях - поставити "латку". Щоб можна було використовувати TASM, подпрогроамма s25 повинна виглядати так:
Особливості та помилки дизассемблера DisDoc 2.3
На жаль, DisDoc 2.3 робить помилки, іноді регулярні, а іноді рідкісні, підступні і навіть підлі. Сама противна помилка - випадковий пропуск даннихвстречается досить рідко. Почнемо з того, що зустрічається дуже часто.
1. EQU - хто тебе вигадав?
У коді, виданому дизассемблером, часто трапляються такі загадкові шматки:
Який сенс присвоєння d0046c equ 00046ch. Щоб з'ясувати це, потрібно відшукати d0046c в тексті. У нашому випадку елемент даних d0046c зустрічається дуже далеко від своєї першої появи - в підпрограмі s321
Отже, щоб виправити цю помилку, необхідно:
- Видалити з початку підпрограми s12 привласнення d0046c equ 00046ch
- Переписати наведений на рис.6 фрагмент s321 наступним чином:
Розглянемо другий приклад. У коді, виданому дизассемблером, зустрівся такий шматок:
Пошук елемента даних d0076a закінчився невдачею. А d00771 зустрівся в такому фрагменті:
Тут явно йде модифікація коду підпрограми s22. Значить, необхідно замінити d00771 на b00771, помітити цієї міткою відповідну інструкцію в s22 і видалити присвоєння
Виправлений фрагмент s22 буде виглядати так:
Розглянемо ще один приклад. На початку s32 зустрілися вже знайомі псевдооператор:
Якщо подивитися в область із зсувами, близькими до С1С, то там виявиться шматок повис коду, який може бути тільки даними:
Тепер потрібно пошукати ідентифікатори d00c1c і d00c1e в тексті, виданому дизассемблером. Дуже швидко можна знайти фрагменти типу: mov WORD PTR ds: d00c1c, ax, mov WORD PTR ds: d00c1e, ax. Значить, помилка дизассемблера полягає в тому, що він переплутав дані і команди і на цьому грунті зробив два неправильних привласнення, equ, що потрапили в початок підпрограми s32.
Виправлення будуть полягати в наступному:
- Прибрати з початку підпрограми s32 два псевдооператор equ.
- Переписати коди на малюнку 7 наступним чином:
На закінчення розглянемо зовсім простенький фрагмент коду:
На закінчення цього пункту підведемо підсумки. Значки equ називають всевдооператорамі. Якщо говорити про дизассемблера DisDoc 2.3, то ця назва дивно точне. Якщо в тексті зустрінеться equ - то помилка поруч. Тим часом, іноді DisDoc 2.3 вживає equ цілком коректно. Так що будьте пильні і не дайте себе обдурити.
2. Погані помилки.
Іноді поведінка дизассемблера важко пояснити. Наприклад, він видає
втрачає або спотворює шматки даних. На щастя, це відбувається дуже рідко.