Перейти к содержанию

Два небольших вопроса по асму.


Рекомендуемые сообщения

Кто-нибудь мне может внятно разъяснить разницу между:


call 0x123

0x123:
...
ret

и


jmp 0x123

0x123:
...
ret (jmp?)

Помимо того, что jmp не трогает стек и если использовать jmp-ret, то надо адрес возврата ручками подгонять. Пока что я заметил разницу только в том, что ret жрёт на 4 байта меньше, чем jmp. call-ret у меня отлично работают, но должна же разница быть. Роюсь в документации, но вдруг кто знает.

И второе. Есть, скажем, такой кусок кода:


pushad
xor esi, esi
xor edi, edi
[b]loop:[/b]
mov dword ptr [esi+0x499434], 0
lea esi, dword ptr [esi+0x98]
inc edi
cmp edi, 0x12c
[b]jne loop[/b]
popad

Каким образом можно избавиться от метки? Да, есть команда loop, есть movsb, но они так и так требуют для работы наличие метки. Или её можно заменить относительным адресом? Можно ли без использования меток организовать переходы по коду? Типа:


push 0x1212
ret

Вот, как-то так. :)

Ссылка на комментарий
Поделиться на другие сайты

Апдейт! Вторую проблему я решил. Почитав документацию, вспомнилось, что можно прыгать не только на метку, но и на адрес. Вот пример:


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. :)

Само собой, этот способ работает и с командами условного перехода.

Первый вопрос остаётся открытым.

Ссылка на комментарий
Поделиться на другие сайты

По первому вопросу.

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(){
//....
}

Ссылка на комментарий
Поделиться на другие сайты

Во, спасибо. Классический code cave выглядит примерно вот так:


В игровом коде:
jmp CaveAddress
nop (балансировка опкодов в случае разной длины инструкций)

CaveAddress:
*какой-то наш код*
jmp ToGameCode

Плюс такого способа в том, что команда jmp не использует стек для хранения адреса возврата - она, скажем так, просто прыгает и всё. Минусов я лично вижу аж пару - во-первых, оба адреса нужно считать (т.к. адрес возврата должен учитывать количество сбалансированных опкодов), во-вторых - команда jmp занимает 4 байта (если не ошибаюсь).

Из неё можно возвращаться и через ret, но для ret нужно засунуть в стек адрес возврата, который будет на 4 байта позже адреса входа. Типа такого:


В игровом коде:
jmp CaveAddress
nop ;(балансировка опкодов)

CaveAddress:
*какой-то наш код*

push CaveAddress+5 ;(кладём в стек адрес возврата = адрес jmp to + по 1 байту на каждый сбалансированный опкод)
ret ;(ret возвращается на адрес из стека)

Тут фишечка в том, что ret занимает то ли 1 то ли 2 байта, но об этом чуть ниже. Соответственно, можно использовать и обратный вариант (jmp вместо ret):


pop eax ;Вытаскиваем из стека адрес возврата
jmp eax ;Прыгаем на него, как будто это ret

Но самое крутое в том, что так как после записи инъекции мы уже в игровой памяти, то мы можем использовать связку call-ret:


Игровой код:
call CodeCave
nop

CodeCave:
*наш код*
ret

Ничего со стеком в этом случае делать не нужно - команда call автоматически кладёт в него адрес возврата. Плюсы в том, что посчитать нужно только один адрес (куда переходить), и места этот код займёт меньше. :)

Ссылка на комментарий
Поделиться на другие сайты

 Короче обычно делают все через прыжки и прыжки рассчитывают.Если лень прыги рассчитывать (или есть другие причины (о них выше)) делаем через call.Да, и call в 32-разрыдных системах имеет размер 5 байт. Также и jmp. А вот short прыги (да-да, такие тоже есть) имеют размер два байта, если не ошибаюсь. 

Ссылка на комментарий
Поделиться на другие сайты

Short-прыжки не катят, т.к. они вроде бы работают в пределах +\-127 байт от текущего адреса, а уменя это может быть и миллион байт - бинарники нынче жирные. :)

В этом случае можно сэкономить при помощи финта ушами:


push 0х123
ret

В итоге это будет эквивалентно команде:

jmp 0x123

Но jmp занимает 5 байт, а вот jmp+ret - 2 байта! tongue.gif

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.