Могу ли я обмануть libc (GLIBC_2.13) в загрузку символа, которого у него нет (из GLIBC_2.15)? - программирование
Подтвердить что ты не робот

Могу ли я обмануть libc (GLIBC_2.13) в загрузку символа, которого у него нет (из GLIBC_2.15)?

В попытке получить "Steam для Linux", работающий над Debian, я столкнулся с проблемой. libcef (Chromium Embedded Framework) отлично работает с GLIBC_2.13 (который может предоставить eglibc при тестировании Debian), но требует от пользователя GLIBC_2.15 (что eglibc не может) одной дозойной дополнительной функции:

$ readelf -s libcef.so | grep -E "@GLIBC_2\.1[4567]"
1037: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.15 (49)
2733: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]@GLIBC_2.15

Мой план атаки здесь состоял в LD_PRELOAD библиотеке прокладки, которая предоставляет только эти функции. Кажется, это не работает. Я действительно хочу избежать установки GLIBC_2.17 (поскольку он находится в Debian, даже у Debian sid все еще есть GLIBC_2.13).


Это то, что я пробовал.

fdelt_chk.c в основном украден из библиотеки GNU C:

#include <sys/select.h>

# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((alias (#name)));

unsigned long int
__fdelt_chk (unsigned long int d)
{
  if (d >= FD_SETSIZE)
    __chk_fail ();

  return d / __NFDBITS;
}
strong_alias (__fdelt_chk, __fdelt_warn)

Мой Versions script выглядит следующим образом:

GLIBC_2.15 {
    __fdelt_chk; __fdelt_warn;
};

Затем я создаю библиотеку следующим образом:

$ gcc -m32 -c -fPIC fdelt_chk.c -o fdelt_chk.o
$ gcc -m32 -shared -nostartfiles -Wl,-s -Wl,--version-script Versions -o fdelt_chk.so fdelt_chk.o

Однако, если я затем запустил Steam (с кучей дополнительных материалов, чтобы заставить его работать в первую очередь), загрузчик все же отказывается найти символ:

% LD_LIBRARY_PATH="/home/tinctorius/.local/share/Steam/ubuntu12_32" LD_PRELOAD=./fdelt_chk.so:./steamui.so ./steam 
./steam: /lib/i386-linux-gnu/i686/cmov/libc.so.6: version `GLIBC_2.15' not found (required by /home/tinctorius/.local/share/Steam/ubuntu12_32/libcef.so)    

Однако символ версии также предоставляется .so только что построенным:

% readelf -s fdelt_chk.so

Symbol table '.dynsym' contains 8 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.3.4 (3)
     2: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     3: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     4: 00000310    44 FUNC    GLOBAL DEFAULT   11 [email protected]@GLIBC_2.15
     5: 00000310    44 FUNC    GLOBAL DEFAULT   11 [email protected]@GLIBC_2.15
     6: 00000000     0 OBJECT  GLOBAL DEFAULT  ABS GLIBC_2.15
     7: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

В этот момент я не знаю, что я могу сделать, чтобы обмануть загрузчика (кто?) в выборе моих символов. Я вообще в правильном направлении?

4b9b3361

Ответ 1

Я столкнулся с этой же проблемой, хотя не с Steam. То, что я пытался запустить, нуждалось в 2.15 для fdelt_chk, в то время как моя система имела 2.14. Я нашел решение для простых случаев, подобных нашим, где мы можем легко обеспечить собственную реализацию отсутствующих функций.

Я начал с вашего попытки решения этой задачи и LD_PRELOAD. Использование LD_DEBUG=all (как было предложено osgx) показало, что компоновщик по-прежнему ищет 2.15, поэтому просто наличие правильного символа было недостаточно, и где-то был какой-то другой механизм управления версиями. Я заметил, что objdump -p и readelf -V показали ссылки на 2.15, поэтому я просмотрел документацию по ELF и нашел информацию о требованиях к версии.

Итак, моей новой целью было преобразовать ссылки на 2.15 в ссылки на что-то еще. Представляется разумным, что я мог бы просто перезаписать структуры, которые ссылались на 2.15 на структуры, которые ссылались на некоторую более низкую версию, например 2.1. В конце концов, после некоторых проб и ошибок, я обнаружил, что просто редактирование правого Elfxx_Vernaux (es?) В .gnu.version_r было достаточно, но, по-моему, обмануть хакера.

Раздел .gnu.version_r представляет собой список из 16-байтовых Elfxx_Verneed и 16-байтовых Elfxx_Vernaux es. Каждой записи Elfxx_Verneed следуют связанные с ней Elfxx_Vernaux es. Насколько я могу судить, vn_file на самом деле, сколько связанных Elfxx_Vernaux es есть, хотя документы говорят number of associated verneed array entries. Это может быть просто недоразумение с моей стороны.

Итак, чтобы начать редактирование, давайте посмотрим на некоторую информацию из readelf -V. Я вырезал части, которые нам не нужны.

$ readelf -V mybinary
<snip stuff before .gnu.version_r>
Version needs section '.gnu.version_r' contains 5 entries:
 Addr: 0x00000000000021ac  Offset: 0x0021ac  Link: 4 (.dynstr)
<snip libraries that don't refer to GLIBC_2.15>
  0x00c0: Version: 1  File: libc.so.6  Cnt: 10
  0x00d0:   Name: GLIBC_2.3  Flags: none  Version: 19
  0x00e0:   Name: GLIBC_2.7  Flags: none  Version: 16
  0x00f0:   Name: GLIBC_2.2  Flags: none  Version: 15
  0x0100:   Name: GLIBC_2.2.4  Flags: none  Version: 14
  0x0110:   Name: GLIBC_2.1.3  Flags: none  Version: 13
  0x0120:   Name: GLIBC_2.15  Flags: none  Version: 12
  0x0130:   Name: GLIBC_2.4  Flags: none  Version: 10
  0x0140:   Name: GLIBC_2.1  Flags: none  Version: 9
  0x0150:   Name: GLIBC_2.3.4  Flags: none  Version: 4
  0x0160:   Name: GLIBC_2.0  Flags: none  Version: 2

Отсюда видно, что сечение начинается с 0x21ac. Каждый указанный файл будет иметь Elfxx_Verneed, за которым следует Elfxx_Vernaux для каждого из подстатей (например, GLIBC_2.3). Я предполагаю, что порядок информации в выходе всегда будет соответствовать порядку в файле, так как readelf просто сбрасывает структуры. Здесь весь раздел .gnu.version_r.

000021A0                                          01 00 02 00
000021B0   A3 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
000021C0   00 00 11 00  32 0D 00 00  10 00 00 00  10 69 69 0D
000021D0   00 00 0B 00  3C 0D 00 00  00 00 00 00  01 00 02 00
000021E0   BE 0C 00 00  10 00 00 00  30 00 00 00  13 69 69 0D
000021F0   00 00 08 00  46 0D 00 00  10 00 00 00  10 69 69 0D
00002200   00 00 07 00  3C 0D 00 00  00 00 00 00  01 00 02 00
00002210   99 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
00002220   00 00 06 00  32 0D 00 00  10 00 00 00  10 69 69 0D
00002230   00 00 05 00  3C 0D 00 00  00 00 00 00  01 00 02 00
00002240   AE 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
00002250   00 00 12 00  32 0D 00 00  10 00 00 00  10 69 69 0D
00002260   00 00 03 00  3C 0D 00 00  00 00 00 00  01 00 0A 00
00002270   FF 0C 00 00  10 00 00 00  00 00 00 00  13 69 69 0D
00002280   00 00 13 00  46 0D 00 00  10 00 00 00  17 69 69 0D
00002290   00 00 10 00  50 0D 00 00  10 00 00 00  12 69 69 0D
000022A0   00 00 0F 00  5A 0D 00 00  10 00 00 00  74 1A 69 09
000022B0   00 00 0E 00  64 0D 00 00  10 00 00 00  73 1F 69 09
000022C0   00 00 0D 00  70 0D 00 00  10 00 00 00  95 91 96 06
000022D0   00 00 0C 00  7C 0D 00 00  10 00 00 00  14 69 69 0D
000022E0   00 00 0A 00  87 0D 00 00  10 00 00 00  11 69 69 0D
000022F0   00 00 09 00  32 0D 00 00  10 00 00 00  74 19 69 09
00002300   00 00 04 00  91 0D 00 00  10 00 00 00  10 69 69 0D
00002310   00 00 02 00  3C 0D 00 00  00 00 00 00

Чтобы кратко рассказать о структуре здесь, она начинается с Elfxx_Verneed. В соответствии с документами мы видим, что будет 2 Elfxx_Vernaux es, одно смещение 16 байт, а следующее Elfxx_Verneed будет смещено 48 байтов. Эти смещения начинаются с начала текущей структуры. Похоже, что технически связанные с ним Elfxx_Vernaux es не могут быть смежными после текущего Elfxx_Verneed, но на самом деле это было во всех файлах, в которые я ткнул.

Из этого мы можем найти файл, который мы хотим (libc.so.6) несколькими способами. Перекрестите ссылку на строку (в которую я не войду), найдите Elfxx_Verneed с количеством 0A 00 (10, соответствующим нашему выходу readelf) или найдите последний Elfxx_Verneed, так как он последний readelf. В любом случае, правильный для моего файла - 0x226C. Его первый Elfxx_Vernaux начинается с 0x227C.

Мы хотим найти Elfxx_Vernaux с версией 0C 00 (12, снова соответствующей нашему выходу readelf выше). Мы видим, что Elfxx_Vernaux, которое соответствует, находится в 0x22CC, а вся структура 95 91 96 06 00 00 0C 00 7C 0D 00 00 10 00 00 00. Мы перепишем первые 12 байтов, чтобы оставить смещение в покое. В конце концов, мы модифицируем данные, а не перемещаемся по структурам.

Чтобы выбрать данные для перезаписи, мы просто скопируем их из другого Elfxx_Vernaux для версии glibc, которую мы можем удовлетворить. Я выбрал один для 2.1, который находится в 0x22EC в моем файле, с данными 11 69 69 0D 00 00 09 00 32 0D 00 00 10 00 00 00. Итак, возьмите первые 12 байтов из этого и перезапишите первые 12 байт выше, и что это для редактирования hex.

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

На этом этапе наша программа все еще не будет работать. Но вместо того, чтобы говорить что-то вроде GLIBC_2.15 not found, он должен жаловаться на отсутствие __fdelt_chk. Теперь мы делаем прокладки и LD_PRELOAD, описанные в вопросе, за исключением того, что вместо версии нашей реализации в качестве 2.15 мы используем версию, которую мы выбрали, в то время как шестнадцатеричное редактирование. На этом этапе программа должна работать.

Этот метод зависит от возможности обеспечить реализацию для любого отсутствующего. Наш __fdelt_chk чрезвычайно прост, но я не сомневаюсь, что в некоторых случаях реализация реализации может быть сложнее, чем просто модернизировать системный libc.

Ответ 2

Для чего это стоит, функция __fdelt_chk связана с функцией FORTIFY_SOURCE, которая была добавлена ​​в glibc 2.15. Он позволяет проверять время компиляции и время выполнения для переполнения буфера.

Если вы смогли перекомпилировать с добавленными CFLAGS, он построил бы обратно совместимый двоичный файл без дополнительной проверки:

-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0