Задача: есть функция подгружаемая из динамической библиотеки, необходимо подменить ее реализацию. К примеру логировать каждый вызов, либо полностью заменить реализацию на свою.
Что можно сделать ?.. Просто вызывать вместо оригинальной функции - свою обертку, в ней выполнять всю нужную логику, а потом вызывать оригинальную функцию. Но так не интересно. Глянем уровнем ниже.
Каким образом происходит вызов функции из динамической библиотеки ? Это не может быть 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
На самом деле в случае первого вызова, все немного сложнее. Для вызова функции динамического линковщика происходит еще пара вызовов трамплинов, которые подготавливают аргументы линковщику.
symbol table
для обозначения того какую именно функцию нужно подгрузить.got
записи, которую нужно будет обновить записав туда адрес загруженной функции