Простое объяснение 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 и диспетчеризация

Алгоритм вызова (упрощённо):

  1. В регистре (например, v0) хранится ссылка на объект.
  2. По этой ссылке определяется реальный класс объекта (может отличаться от указанного в инструкции).
  3. У класса есть таблица виртуальных методов (vtable).
  4. По индексу метода (из метаданных DEX) находится нужная реализация.
  5. Выполняется переход в код найденного метода.

Важно: если объект — экземпляр подкласса, то будет вызван переопределённый метод подкласса, даже если в 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‑фрагмент и показать, какая реализация вызовется и как правильно его патчить.