Простое объяснение invoke-virtual в Smali
invoke-virtual — это DEX‑инструкция для вызова виртуального метода экземпляра (аналог obj.method()). Конкретная реализация выбирается во время выполнения по фактическому классу объекта через vtable; аргументы передаются в регистрах, а результат возвращается через move-result*.
Первый регистр в фигурных скобках invoke-virtual обычно — это объект (this), остальные — аргументы.
Как читать invoke-virtual в Smali
Типичная строка Smali: text invoke-virtual {v0, v1}, Lcom/example/User;->setName(Ljava/lang/String;)V
Разбор:
- invoke-virtual — тип вызова (виртуальный).
- {v0, v1} — регистры: v0 — ссылка на объект, v1 — аргумент.
- Lcom/example/User; — класс, где объявлен метод (для сигнатуры).
- setName — имя метода.
- (Ljava/lang/String;)V — сигнатура (вход: String, возврат: void).
Проще: это низкоуровневый эквивалент user.setName(name), где конкретный метод может быть переопределён.
Что происходит "под капотом" — vtable и диспетчеризация
Алгоритм вызова (упрощённо):
- В регистре (например, v0) хранится ссылка на объект.
- По этой ссылке определяется реальный класс объекта (может отличаться от указанного в инструкции).
- У класса есть таблица виртуальных методов (vtable).
- По индексу метода (из метаданных DEX) находится нужная реализация.
- Выполняется переход в код найденного метода.
Важно: если объект — экземпляр подкласса, то будет вызван переопределённый метод подкласса, даже если в Smali указан базовый класс.
Нельзя вставлять другие инструкции между invoke-virtual и следующей move-result*. Это вызовет ошибку валидации DEX.
Регистры, move-result и /range
В DEX нет стека JVM — все через регистры. Форма вызова передаёт объект и аргументы в фигурных скобках. Если метод возвращает значение, результат берётся инструкцией move-result, которая должна идти сразу после invoke-virtual.
Пример: text invoke-virtual {p0, p1, p2}, Lpkg/Class;->sum(II)I move-result v0
Если аргументов много и они лежат подряд, используется invoke-virtual/range с указанием диапазона регистров.
Отличия invoke-virtual от других invoke-*
Краткая таблица различий
Сравнение типов invoke в Smali
| Инструкция | Для каких методов | Нужен объект | Полиморфизм | Java‑аналог |
|---|---|---|---|---|
| invoke-virtual | Обычные виртуальные методы | Да | Да | obj.method() |
| invoke-direct | Конструкторы, private, super | Да | Нет | new, this(), super.method() |
| invoke-static | Статические методы | Нет | Нет | Class.method() |
| invoke-interface | Методы интерфейсов | Да | Да (через интерфейсную таблицу) | iface.method() |
| invoke-super | Вызов реализации суперкласса | Да | Нет (фиксировано) | super.method() |
| invoke-virtual/range | То же, с множеством аргументов | Да | Да | — |
Где это полезно при анализе и патчинге
- Поиск вызова интересующей логики: искать сигнатуры методов и смотреть invoke-virtual‑вызовы — это быстрый путь понять, где метод реально используется.
- Хуки и инжекты (Frida, Xposed): знать тип вызова помогает правильно перехватить реализацию.
- Патчинг APK: можно заменить invoke-virtual на другой вызов или подменить метод, но нужно учитывать семантику (статические vs экземплярные методы).
Пример патча (заставить результат всегда true):
- Оригинал: text invoke-virtual {v0}, Lcom/example/Feature;->isEnabled()Z move-result v2
- Патч: text const/4 v2, 0x1 (удалить или закомментировать invoke-virtual и move-result)
При разборе Smali сначала определите тип invoke-*, затем изучайте тело вызываемого метода — это ускоряет понимание.
Частые ошибки
- Пытаются вставить инструкции между invoke-virtual и move-result — приводит к ошибкам валидации.
- Считают, что в инструкции указанная в ней класс — это обязательно класс объекта. На самом деле реализация выбирается по фактическому типу объекта.
- Забирают результат без move-result — тогда значение не попадёт в регистр.
FAQ
-
Что произойдёт при invoke-virtual на null?
Как и в Java — NullPointerException (в процессе выполнения будет бросаться NPE). -
Почему иногда виден базовый класс в инструкции, а вызывается метод другого класса?
В инструкции указывается класс для сигнатуры; реальная реализация определяется во время выполнения по классу объекта. -
Когда использовать invoke-virtual/range?
Когда много аргументов идут подряд в регистрах — /range упрощает передачу большого блока регистров. -
Можно ли заменить invoke-virtual на invoke-static?
Теоретически да, но нужно подготовить статический метод (например, добавить параметр для this) и скорректировать вызовы и сигнатуры — без этого приложение сломается.
Если нужно, могу разобрать конкретный Smali‑фрагмент и показать, какая реализация вызовется и как правильно его патчить.