Адрес завершения функции в дизассемблере. Как определть?

Тема в разделе "WASM.RESEARCH", создана пользователем OldDino, 17 май 2007.

  1. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Пытаюсь написать свой дизасемблер. Непосредственно декодер команд сделал достаточно быстро. Но хотелось бы добавить дизассемблеру хоть немного мозгов, то есть интеллектуальности. В качестве первого шага я хотел бы выделить функции. Начало функции пока выделяю по call'ам. А как найти конец функции? Есть ли какой-нибудь алгоритм, который выловит 100% завершений функции?

    С уважением,

    OldDino
     
  2. rain

    rain New Member

    Публикаций:
    0
    Регистрация:
    22 апр 2006
    Сообщения:
    976
    Может сделать так? : смотришь в функции jmpы, если их нет, то первый рет и есть конец, если есть джампы за этот рэт то соответственно первый рет за джампом на самое дальнее расстояние в функции и будет конец. Но наверно лучше не на кол\рет смотреть а на базу эпилогов и прологов, олька непример никак не выделят __declspec(naked) функции. Это так только мысли потому что дизасм я не писал. Если не секрет скока времени на его уже ушло у тебя, что умеет твое творение?

    rain
     
  3. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    я думаю, что нахождение прологов/эпилогов выделит большинство функций... останутся только naked и те, которые подверглись оптимизации..
     
  4. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Благодарю за ответ. Но в ответе не предусмотрен, к примеру, такой случай - функцию многократно патчили, из функции есть какой-то jmp, скажем, в конец программы, оттуда - ещё один jmp, и так несколько раз. Последний jmp в этом списке, к примеру, возвращает управление в функцию. Естественно, это маловероятно, но возможно. Как мне поступать в данном случае? У IDA, по-моему, такие вещи называются "function chunk". Как мне продраться сквозь залежи этих "funcnion chunk'ов", если они появятся?

    Ну, и, попутно, в ответе есть одно слово, которое заставило меня обследовать ещё один закоулочек в дизассемблировании, о котором я почему-то не подумал. Так что ещё раз благодарю.

    Времени на разработку ушло относительно немного, я перебрал CADT для дизассемблирования отдельных инструкций, затем, используя знания о формате PE-файла и Researcher, написал некоторое подобие препроцессора, что дало мне массу интересной информации. А затем уже написал непосредственно дизассемблер (отслеживание call'ов, переходов и т.д.).

    Пока "творение" не умеет фактически ничего. Однако, пользу это "творение" уже принесло. Например, я сравнил получаемые мною результаты с IDA. Честно говоря, число распознанных функций меня не порадовало, IDA распознал много больше. С другой стороны, я увидел, что IDA зачастую "косячит", Researcher здорово помог мне. Ну, и, наконец, видно, что IDA - это не просто дизассемблер, он анализирует не только call'ы и jmp'ы, но и ещё что-то. Хотелось бы, конечно, это "что-то" найти.

    С уважением,

    OldDino
     
  5. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Благодарю. Но и в этом ответе, к сожалению, не разрешена проблема, которую я описал в ответе rain'у. А так хочется решить эту проблему...

    С уважением,

    OldDino
     
  6. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Это результат не патча, а сильной оптимизации. Если компилеру не нравится функция, он для увеличения производительности может раскидать ее код по всему телу программы, если надо.
    Тогда функция будет выглядеть так:
    function:
    ...
    jmp chunk1

    ...

    chunk1:
    ...
    jmp chunk2

    ...

    chunk2:
    ...
    ret

    Ну вот сравнивай свои результаты с идой и смотри, чем отличается. Это даст ключ к пониманию
     
  7. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Благодарю. Принято. То есть цепочка jmp'ов В ЛЮБОМ СЛУЧАЕ должна возвращать управление в тело функции. И даже если ret находится очень далеко от начала функции, можно допустить одну из двух возможностей:
    1) основное тело завершается на команде перехода на chunk;
    2) основное тело (при наличии ссылок на команды, следующие за jmp'ом) завершается ret'ом, а если ret отсутствует, то перед началом следующей функции.

    Тогда понятие "функция" (subroutine в терминологии IDA ) может быть заменено на понятие "список (возможно, граф) chunk'ов". И тогда это меняет всё дело. В свою очередь, chunk'и сами по себе могут быть функциями, ну, и так далее.

    Варианты "push + jmp" можно считать идиомами и обрабатывать их особо.

    Верно?

    С уважением,

    OldDino
     
  8. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    В смысле в тело? В моем примере по RET'у управлене ускачет в вызвавшую программу.
    Основное тело чего? Функции? Тогда оно завершается при встрече эпилога. Надо только по чанкам пройтись.
    как ты себе представляешь это. идет одна функция, потом без рета вторая?
    ну они не вызываются как функции в обычном смысле этого слова, через call. им просто передается управление из другого чанка или тела функции через jmp
    Я таких не встречал
     
  9. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Оговорился. Именно в вызывающую программу.

    1) основное тело завершается на команде перехода на chunk;
    Основное тело чего? Функции? Тогда оно завершается при встрече эпилога. Надо только по чанкам пройтись.

    А разве не может быть, что эпилога в основном теле нет, а он есть только в chunk'е? Так, как в приведённой примере? Я об этом варианте говорю.

    как ты себе представляешь это. идет одна функция, потом без рета вторая?

    Тот же самый вариант! То есть функция, в которой есть переход на chunk, а ret - в chunk'е.

    ну они не вызываются как функции в обычном смысле этого слова, через call. им просто передается управление из другого чанка или тела функции через jmp

    Мне они неоднократно попадались. push + jmp - это попытка скрыть call, не более. В этом случае их и обрабатывать-то особо не нужно, просто пометил, что call, и всё.

    Благодарю, уже узнал много нового и интересного.

    С уважением,

    OldDino
     
  10. rain

    rain New Member

    Публикаций:
    0
    Регистрация:
    22 апр 2006
    Сообщения:
    976
    Вчера ковырял прогу написаную на дельфе, олик очень часто ошибался, дело в том что функции имеют по несколько эпилогов, а олек определял конец функции - первый эпилог.

    Вы такое часто видели?

    А существует ли вообще универсальный 100% алго разбития бинарника на функции ? Что стажете про километровый код написаный на асме имеющий call'ы в себя самого, ждампы на любой участок себя в том числе и внутрь этих cаll'ов, и завершающийся где угодно и как угодно например вызовом ExitProcess, без рет'а, или джампом на ZwExitThread ? Может стоит отказаться от такой идеии и ввести дополнительные условия при которых отказываться выделять функцию из бинарника (наприм максимальная длина джампов, или call $+5)
     
  11. OldDino

    OldDino New Member

    Публикаций:
    0
    Регистрация:
    3 июл 2006
    Сообщения:
    44
    Если взглянуть в моё первое сообщение, то там хорошо просматривается сомнение в части существования подобного алгоритма. Видно, придётся оговорить, что, мол, программа работает в подавляющем большинстве случаев, но вот в таком случае, в таком и вот в таком - не работает, уж извините. Мы же не волшебники, мы только учимся...

    С уважением,

    OldDino
     
  12. dermatolog

    dermatolog Member

    Публикаций:
    0
    Регистрация:
    3 фев 2005
    Сообщения:
    406
    Адрес:
    Екатеринбург
    JMP вперед очень часто втсречается на циклах, нужно дизасмить в фоновом режиме адрес из JMP и смотреть - если обратные переходы на код, откуда ты только что пришел, если есть Jcc, то это 100% цикл и тогда можно включать этот адрес в тело твоей процедуры. Если по JMP ты не видишь обратных переходов, то это скорее всего уже другая функция (либо кусок от другой функции) - тогда лучше остановиться :))
     
  13. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Так обычно и бывает
    Жизнь так сложилась, что часто ;)
    Что-то мне подсказывает, что нет.
    обычно это недругая функция, а chunk всё той же )
     
  14. Stiver

    Stiver Партизан дзена

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    812
    Адрес:
    Germany
    OldDino
    Я бы начал с определения "функции" - что это такое на уровне машинного кода? В зависимости от определения уже можно работать дальше.

    Одно из возможных определений: кусок кода, не обязательно последовательный, с одним входом (с количеством вызовов = прыжков на него в программе >1, так как иначе зачем такая функция нужна) и одним или более выходами, не пересекающаяся с другими функциями. (Вставить еще какое-нибудь условие про максимальность, то есть нет кода вне функций)

    Тогда можно построить block graph кода, иерархический скорее всего, и разложить его на подходящие под определение куски. Потом вход и выходы каждого куска проверить на наличие характерных особенностей - пролог, вызов через call и т.д. Чем больше признаков присутствуют, тем с большей вероятностью этот кусок был функцией в исходном коде до компиляции. Установить пороговое значение этой вероятности, при превышении автоматически считать функцией, в противном случае помечать как вероятную функцию и пусть пользователь говорит, хочет ли он такие видеть.

    P.S. Кстати примерно так должен работать refactoring по выделению новых функций, только там еще сравнение кусков кода на идентичность.
     
  15. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    хм.. кстати была интересная мысль по поводу оптимизации.. вдруг компилятор пересечет некоторые участки кода, одинаковые у обоих функций (или, если они были похожими, перекомпилирует функции, чтобы они были одинаковыми), и этот кусок будет чанком обоих функций..
    то есть обе функции будут на него ссылаться. конечно, это должен быть конец, иначе выход обратно не определен.
    то есть если концовки, например, 5 функций совпадают, то можно его всунуть в код только один раз, а в конце остальных четырех функций сделать JMP на него.
    я вроде бы такое видел в ядре.. когда делался один раз типа
    MOV EAX, C0000005 / LEAVE / RET и из очень многих мест и даже разных функций на него были JMP'ы.
    Так что с пересечениями придется учесть этот вариант

    А насчет вероятностей, полностью согласен
     
  16. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    1) Сущ. case table с вызовом типа call [0x12345678+eax*4]. Обычно адреса по адресу 0x12345678 указывают на часть функции. Причем нужно правильно определить число адресов по 0x12345678.
    2) При оптимизации, call перед ret'ом часто заменяется на jmp.
    3) Как пример, в функции strcat, после определения конца строки, к которой присоединяется вторая строка, вообще нет ни jmp'ов, ни call'ов - сразу начинается ф-ция strcpy.
    4) Если не ошибаюсь, в дельфях существует механизм вложенных функций, когда одна функция внутри другой. Ну, во всяком случае, так можно легко сделать на асме, пример можно найти где-нибудь в ResEd, HexEd от KetilO.
     
  17. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    5) В сях после вызова exit последнее время не вижу ни pop regs, ни ret. Ну, ессно после ExitProcess в асме тоже.
     
  18. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    6) ЕУсли fasm проги дизасмить, после call'а иногда идет не код, а текст :)
     
  19. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    не всегда ) но иногда. к тому же, это особенность не фасма, а макросов в нем. можно так изменить макрос pushd, чтобы такого не случалось. Я, кстати,у себя изменил. чтобы вместо

    call _t
    string db 'hello',0
    _t:

    генерилось:

    push string
    jmp _t
    string db 'hello', 0
    _t:
     
  20. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    Great
    IMHO, лучше оставить как есть, но с комментраием. Или дать юзеру возможность выбрать..