Задача: есть функция подгружаемая из динамической библиотеки, необходимо подменить ее реализацию. К примеру логировать каждый вызов, либо полностью заменить реализацию на свою.

Что можно сделать ?.. Просто вызывать вместо оригинальной функции - свою обертку, в ней выполнять всю нужную логику, а потом вызывать оригинальную функцию. Но так не интересно. Глянем уровнем ниже.

Каким образом происходит вызов функции из динамической библиотеки ? Это не может быть call по фиксированному смещению, ведь мы не знаем заранее по какому адресу будет подгружена библиотека, да и само приложение (ASLR)

Во время выполнения приложения фиксированы только относительные смещения между секциями данных, за счет этого все вызовы и работают. Но даже по относительному смещению функции из динамически подгружаемой библиотеки нет на момент компиляции кода. Поэтому вместо вызова конкретной функции – происходит вызов до специальной секции .PLT , где добавляется “трамплин” до нужного кода. Если в коде вызывать функцию write(...), поставить на этот вызов breakpoint , а потом попросить lldb сделать stepi , то можно увидеть этот самый “трамплин”

    0x104ccab44 <+0>: adrp   x16, 6
    0x104ccab48 <+4>: ldr    x16, [x16, #0xd0]
    0x104ccab4c <+8>: br     x16

Так выглядит трамплин на процессоре ARM v8.4 на iOS.

| adrp x16, 6 | на ARM нет возможности напрямую взять значение регистра $pc хранящего адрес текущей выполняемой инструкции. adrp позволяет взять страницу адреса текущей исполняемой инструкции и прибавить к ней смещение еще в N(6) страниц и сохранить результат в заданный регистр x16. Размер страницы 4096 байт. page(0x104ccab44) = 0x104cca000 0x104cca000 + 6*4096 = 0x104cd0000 | | --- | --- | | ldr x16, [x16, #0xd0] | загружаем в регистр x16 значение взятое по адресу из регистра x16 плюс 0xd0 , т.е. по адресу 0x104cd00d0 | | br x16 | происходит вызов кода по адресу в регистре x16 |

Как видим вызов из трамплина идет по адресу, который берется из памяти. Если попросить lldb вывести информацию о секциях памяти исполняемого приложения:

(lldb) image dump sections
Dumping sections for 439 modules.
Sections for '/Users/smirnov/Library/Developer/Xcode/DerivedData/Hijack-fpespqyowsuyyfaelvmgaqnksdcu/Build/Products/Debug-iphoneos/Hijack.app/Hijack' (arm64):
  SectID     Type             Load Address                             Perm File Off.  File Size  Flags      Section Name
  ---------- ---------------- ---------------------------------------  ---- ---------- ---------- ---------- ----------------------------
  0x00000100 container        [0x0000000000000000-0x0000000100000000)* ---  0x00000000 0x00000000 0x00000000 Hijack.__PAGEZERO
  0x00000200 container        [0x0000000104c60000-0x0000000104cd0000)  r-x  0x00000000 0x00070000 0x00000000 Hijack.__TEXT
  0x00000001 code             [0x0000000104c67db8-0x0000000104ccaa18)  r-x  0x00007db8 0x00062c60 0x80000400 Hijack.__TEXT.__text
  0x00000002 code             [0x0000000104ccaa18-0x0000000104ccab50)  r-x  0x0006aa18 0x00000138 0x80000408 Hijack.__TEXT.__stubs
  0x00000003 regular          [0x0000000104ccab50-0x0000000104ccac2c)  r-x  0x0006ab50 0x000000dc 0x00000000 Hijack.__TEXT.__objc_methlist
  0x00000004 data-cstr        [0x0000000104ccac2c-0x0000000104cce46b)  r-x  0x0006ac2c 0x0000383f 0x00000002 Hijack.__TEXT.__cstring
  0x00000005 regular          [0x0000000104cce470-0x0000000104cce4cc)  r-x  0x0006e470 0x0000005c 0x00000000 Hijack.__TEXT.__const
  0x00000006 data-cstr        [0x0000000104cce4cc-0x0000000104ccf31d)  r-x  0x0006e4cc 0x00000e51 0x00000002 Hijack.__TEXT.__objc_methname
  0x00000007 data-cstr        [0x0000000104ccf31d-0x0000000104ccf38d)  r-x  0x0006f31d 0x00000070 0x00000002 Hijack.__TEXT.__objc_classname
  0x00000008 data-cstr        [0x0000000104ccf38d-0x0000000104ccfec3)  r-x  0x0006f38d 0x00000b36 0x00000002 Hijack.__TEXT.__objc_methtype
  0x00000009 compact-unwind   [0x0000000104ccfec4-0x0000000104ccfffc)  r-x  0x0006fec4 0x00000138 0x00000000 Hijack.__TEXT.__unwind_info
  0x00000300 container        [0x0000000104cd0000-0x0000000104cd4000)  rw-  0x00070000 0x00004000 0x00000010 Hijack.__DATA_CONST
  0x0000000a data-ptrs        [0x0000000104cd0000-0x0000000104cd00d8)  rw-  0x00070000 0x000000d8 0x00000006 Hijack.__DATA_CONST.__got

...skipped for brevity...

Можно увидеть, что адрес принадлежит секции .GOT , что расшифровывается, как Global Offset Table именно эта таблица позволяет не знать реальных адресов функций на момент компиляции приложения, а также позволяет объявлять extern переменные, значения которых будут заданы в другом коде.

При первом вызове функции из динамической библиотеки в .got будет лежать адрес до функции динамического линковщика, который подгрузит нужную библиотеку и проставит новый адрес в .got

На самом деле в случае первого вызова, все немного сложнее. Для вызова функции динамического линковщика происходит еще пара вызовов трамплинов, которые подготавливают аргументы линковщику.

  1. Смещение в symbol table для обозначения того какую именно функцию нужно подгрузить
  2. Адрес .got записи, которую нужно будет обновить записав туда адрес загруженной функции