keng Опубликовано 24 декабря, 2011 Поделиться Опубликовано 24 декабря, 2011 Кто-нибудь мне может внятно разъяснить разницу между:call 0x1230x123:...retиjmp 0x1230x123:...ret (jmp?)Помимо того, что jmp не трогает стек и если использовать jmp-ret, то надо адрес возврата ручками подгонять. Пока что я заметил разницу только в том, что ret жрёт на 4 байта меньше, чем jmp. call-ret у меня отлично работают, но должна же разница быть. Роюсь в документации, но вдруг кто знает.И второе. Есть, скажем, такой кусок кода:pushadxor esi, esixor edi, edi[b]loop:[/b]mov dword ptr [esi+0x499434], 0lea esi, dword ptr [esi+0x98]inc edicmp edi, 0x12c[b]jne loop[/b]popadКаким образом можно избавиться от метки? Да, есть команда loop, есть movsb, но они так и так требуют для работы наличие метки. Или её можно заменить относительным адресом? Можно ли без использования меток организовать переходы по коду? Типа:push 0x1212retВот, как-то так. Ссылка на комментарий Поделиться на другие сайты Поделиться
keng Опубликовано 24 декабря, 2011 Автор Поделиться Опубликовано 24 декабря, 2011 Апдейт! Вторую проблему я решил. Почитав документацию, вспомнилось, что можно прыгать не только на метку, но и на адрес. Вот пример:beep: ;Меткаinvoke Beep,500,200 ;Играем звук высотой в 500 Гц и длительностью в 200 мсекjmp beep ;Прыгаем обратно на меткуПосле компиляции метка заменится адресом вида 0x12345, с которого будет начинаться команда воспроизведения звука и в итоге мы будем рекурсивно прыгать и выполнять одну и ту же команду. Проблема может возникнуть в том случае, если компилятора у нас нет, а нужно записать такую конструкцию в память. Зная, что команда invoke Beep,500,200 занимает 10 байт (можно посмотреть в дизассемблере), мы можем переписать этот участок следующим образом:invoke Beep,500,200 ;Играем звукjmp $-15 ;Прыгаем на 15 байт назад относительно текущего адресаЗначок $ нужен затем, чтобы прыгать именно на адрес раньше 15-ти байт команды jmp, т.к. сама команда так же занимает 5 байт. Получается, что прыгаем мы на начало команды invoke. Само собой, этот способ работает и с командами условного перехода.Первый вопрос остаётся открытым. Ссылка на комментарий Поделиться на другие сайты Поделиться
MasterGH Опубликовано 25 декабря, 2011 Поделиться Опубликовано 25 декабря, 2011 По первому вопросу. 1) Если использовать call, то для возврата желательно использовать ret.2) Если используем jmp, то для возврата должен быть другой jmp.Второй пункт явно быстрее первого по выполнению инструкций. Но в то же время первый пункт даёт очень сильное преимущество, когда два и более call-ов "прозванивают" какой-то адрес, тогда по ret будет возврат в место под тем call, который был выполнен. Короче говоря call это своего рода функции.int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ SomeFunction(); // возращает eax из call //.... SomeFunction();// возращает eax из call //.... SomeFunction();// возращает eax из call //.... SomeFunction();// возращает eax из call //.... SomeFunction();// возращает eax из call //.... } int SomeGunction(){ //.... } Ссылка на комментарий Поделиться на другие сайты Поделиться
keng Опубликовано 25 декабря, 2011 Автор Поделиться Опубликовано 25 декабря, 2011 Во, спасибо. Классический code cave выглядит примерно вот так:В игровом коде:jmp CaveAddressnop (балансировка опкодов в случае разной длины инструкций)CaveAddress:*какой-то наш код*jmp ToGameCodeПлюс такого способа в том, что команда jmp не использует стек для хранения адреса возврата - она, скажем так, просто прыгает и всё. Минусов я лично вижу аж пару - во-первых, оба адреса нужно считать (т.к. адрес возврата должен учитывать количество сбалансированных опкодов), во-вторых - команда jmp занимает 4 байта (если не ошибаюсь).Из неё можно возвращаться и через ret, но для ret нужно засунуть в стек адрес возврата, который будет на 4 байта позже адреса входа. Типа такого:В игровом коде:jmp CaveAddressnop ;(балансировка опкодов)CaveAddress:*какой-то наш код*push CaveAddress+5 ;(кладём в стек адрес возврата = адрес jmp to + по 1 байту на каждый сбалансированный опкод)ret ;(ret возвращается на адрес из стека)Тут фишечка в том, что ret занимает то ли 1 то ли 2 байта, но об этом чуть ниже. Соответственно, можно использовать и обратный вариант (jmp вместо ret):pop eax ;Вытаскиваем из стека адрес возвратаjmp eax ;Прыгаем на него, как будто это retНо самое крутое в том, что так как после записи инъекции мы уже в игровой памяти, то мы можем использовать связку call-ret:Игровой код:call CodeCavenopCodeCave:*наш код*retНичего со стеком в этом случае делать не нужно - команда call автоматически кладёт в него адрес возврата. Плюсы в том, что посчитать нужно только один адрес (куда переходить), и места этот код займёт меньше. Ссылка на комментарий Поделиться на другие сайты Поделиться
MasterGH Опубликовано 25 декабря, 2011 Поделиться Опубликовано 25 декабря, 2011 Короче обычно делают все через прыжки и прыжки рассчитывают.Если лень прыги рассчитывать (или есть другие причины (о них выше)) делаем через call.Да, и call в 32-разрыдных системах имеет размер 5 байт. Также и jmp. А вот short прыги (да-да, такие тоже есть) имеют размер два байта, если не ошибаюсь. Ссылка на комментарий Поделиться на другие сайты Поделиться
keng Опубликовано 26 декабря, 2011 Автор Поделиться Опубликовано 26 декабря, 2011 Short-прыжки не катят, т.к. они вроде бы работают в пределах +\-127 байт от текущего адреса, а уменя это может быть и миллион байт - бинарники нынче жирные. В этом случае можно сэкономить при помощи финта ушами:push 0х123retВ итоге это будет эквивалентно команде:jmp 0x123Но jmp занимает 5 байт, а вот jmp+ret - 2 байта! Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения