Перепрыгивает / удаляет заголовок программы `PHDR` в файле ELF для исполняемого файла ОК? Если так, то почему? - PullRequest
2 голосов
/ 03 мая 2020

Я занимался хакерством в двоичном коде для этой простой программы на C ++, чтобы понять заголовки программ для ELF:

int main(){ }

, скомпилированных с:

❯ make
g++ -O0 -fverbose-asm -no-pie -o main main.cpp

Я использовал readelf -l main для получить следующее:

Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000004c0 0x00000000000004c0  R      0x1000
...

Я вижу в этой документации: http://man7.org/linux/man-pages/man5/elf.5.html для PHDR:

Элемент массива, если присутствует, задает loca - количество и размер самой таблицы заголовков программы, как в файле, так и в образе памяти программы. Этот тип сегмента может встречаться в файле не более одного раза. Более того, это может произойти, только если таблица заголовков программы является частью образа памяти программы. Если он присутствует, он должен предшествовать любой загружаемой записи сегмента.

Наличие if present в цитате заставило меня задуматься, что произойдет, если я просто перепрыгну через заголовок PHDR. Я использовал шестнадцатеричный редактор vim, чтобы изменить двоичный формат main, используя :%!xxd (обязательно запустите :%!xxd -r перед сохранением, иначе это уже не двоичный файл), чтобы получить:

00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000  ..>..... .@.....
00000020: 4000 0000 0000 0000 1839 0000 0000 0000  @........9......

до:

00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000  ..>..... .@.....
00000020: 7800 0000 0000 0000 1839 0000 0000 0000  @........9......

(только изменение 20-го байта), чтобы перепрыгнуть через длину заголовка PHDR. Я снова запускаю readelf, чтобы убедиться, что это по-прежнему действительный файл ELF:

❯ readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 120

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  ...

И, что удивительно, программа все еще прекрасно работает. Зачем нам вообще нужен заголовок PHDR? Полезно ли это для ссылок и / или других ситуаций? Кажется, что он вообще не используется во время выполнения, так почему у нас это лежит?

Ответы [ 2 ]

3 голосов
/ 03 мая 2020

Если основная программа имеет тип ET_EXEC (не P IE), возможно, она работает без PT_PHDR. Основное использование PT_PHDR - возможность сравнивать (не перемещенный) адрес в заголовке с фактическим адресом времени выполнения заголовков программы (полученным с помощью компоновщика Dynami c через AT_PHDR в вспомогательном векторе), чтобы определить смещение, при котором был загружен исполняемый файл P IE.

Я не уверен, каковы требования динамического c компоновщика glib * для наличия PT_PHDR, но в musl lib c нам это нужно только для вычисления этого смещения нагрузки, а в остальном он вообще не используется.

2 голосов
/ 03 мая 2020

Я снова запускаю readelf, чтобы убедиться, что это все еще действительный файл ELF:

Обратите внимание, что, хотя действительный ELF, он теперь испортил 11-ю запись в таблице заголовков программы (поскольку вы не уменьшал количество заголовков программы).

И, что удивительно, программа все равно прекрасно работает.

Эта программа не использует любой особенности динамического компоновщика c, поэтому тот факт, что вы повредили его структуры, не проявляется.

Теперь попробуйте вызвать некоторые подпрограммы из libc.so.6 или вызвать dlopen и dlsym, и посмотрите, работает ли он по-прежнему.

Глядя на источник загрузчика GLIB C ( rtld. c), он очень заботится о PT_PHDR, поэтому Я был бы удивлен, если бы все еще работало без него.

...