Авторы расширений, хакеры браузера, встречайте js-ctypes

Автор оригинала: Dan Witte
  • Перевод
  • Tutorial
Что такое (можете спросить вы) js-ctypes? Скажем, пишете вы расширение на джаваскрипте, и ему нужно обратиться к местному коду (native code). Например, weave-crypto приходится обращаться к библиотеке NSS. А ваше расширение может пожелать, например, вызвать напрямую NSPR, libc, или функции Win32. Прямо сейчас есть два выхода: либо использовать скриптуемые XPCOM-интерфейсы (обеспечиваемые libxul), либо написать и реализовать собственные XPCOM-интерфейсы, то есть поставлять двоичный код в своём расширении. Если первый вариант не годится, остаётся только второй, но тогда поставлять расширение становится заметно сложнее: приходится отдельно компилировать двоичный код для каждой из поддерживаемых платформ, чтобы упаковать его внутрь вашего кросс-платформенного xpi.

Ответом на эту трудность поэтому станет библиотека js-ctypes: она позволяет джаваскрипту вызывать местный код (написанный на Си) и манипулировать сишными типами данных, без использования XPCOM, и нет необходимости компилировать ни одну строку кода. Это означает, что вам не придётся определять XPCOM-интерфейсы, и что можно будет использовать разделяемые библиотеки (shared libraries), подобные libc, напрямую. Есть и побочный положительный эффект: мы большей частью устраняем потери на преобразование типов данных, свойственные XPConnect, так что выполнение кода может становиться быстрее. (Сравнение скорости я приведу в одной из последующих блогозаписей.) Библиотека js-ctypes будет поставляться с Gecko 1.9.3, а эта платформа (если номера версий не переменятся) станет фундаментом для Firefox 3.7.

Вы можете спросить: «Но как...?». И вот примеры (они проверялись на 32-битных Линуксах x86 и содержат не кросс-платформенные части):

1) Открытие библиотеки.
// Сперва импортируем модуль ctypes.
Components.utils.import("resource://gre/modules/ctypes.jsm");

// Открываем libc.
let library = ctypes.open("libc.so.6");

// Определяем функцию fopen, имеющую следующий Си-прототип:
//  FILE* fopen(const char* name, const char* mode);
let fopen = library.declare("fopen",                     // имя символа
                            ctypes.default_abi,          // конвенция вызова cdecl
                            ctypes.PointerType("FILE*"), // возвращаемый функцией тип (FILE*)
                            ctypes.char.ptr,             // первый аргумент (const char*)
                            ctypes.char.ptr);             // второй аргумент (const char*)

// Вызываем функцию, она возвращает объект указателя FILE*.
let file = fopen("hello world.txt", "w");

// Что такое 'file'?
file.toString();
// выдаст "ctypes.PointerType("FILE*")(ctypes.UInt64("0x9781b38"))" (значение указателя)

// ... далее пишем данные в файл ...


* This source code was highlighted with Source Code Highlighter.

2) Определение и использование структурных типов (struct) и массивов.
// Определяем struct по имени 'hostent', содержащий пять полей,
// каждое из которых имеет свой тип.
// Вот соответствующее определение на Си:
//  struct hostent {
//     char* h_name;      // имя хоста
//     char** h_aliases;  // массив строк, содержащих псевдонимы хоста
//     int h_addrtype;     // используется ли адрес IPv4 или IPv6
//     int h_length;      // длина (в байтах) IP-адреса
//     char** h_addr_list; // массив IP-адресов (от нэймсервера)
//  };
let hostent = ctypes.StructType("hostent",
                                [{ h_name     : ctypes.char.ptr                 },
                                 { h_aliases  : ctypes.char.ptr.ptr             },
                                 { h_addrtype : ctypes.int                     },
                                 { h_length    : ctypes.int                     },
                                 { h_addr_list : ctypes.uint8_t.array(4).ptr.ptr }]);

// Определяем функцию 'gethostbyname', имеющую нижеследующий Си-прототип:
//  struct hostent* gethostbyname(const char* name);
let gethostbyname = library.declare("gethostbyname",
                                    ctypes.default_abi,
                                    hostent.ptr,         // используем наш тип 'hostent'
                                    ctypes.char.ptr);

// Просим нашу функцию обработать имя хоста.
let google = gethostbyname("mail.google.com");

// По полученному указателю получим struct типа 'hostent',
// обращаемся к его полю 'h_name', и преобразуем к джаваскриптовой
// строке. Наши действия примерно подобны вот этому оператору Си:
//  printf("%s", google->h_name);
google.contents.h_name.readString();
// выдаст "googlemail.l.google.com"

// Дважды следуя по указателю из поля 'h_addr_list',
// получим первый элемент массива, который и сам является
// массивом из четырёх байтов IPv4-адреса хоста.
// Вот примерный Си-эквивалент:
//  printf("%u.%u.%u.%u", (int) h_addr_list[0][0], (int) h_addr_list[0][1],
//         (int) h_addr_list[0][2], (int) h_addr_list[0][3]);
google.contents.h_addr_list.contents.contents.toString();
// выдаст "ctypes.uint8_t.array(4)([74, 125, 19, 17])", то есть 74.125.19.17


* This source code was highlighted with Source Code Highlighter.

3) Создание Си-подобных указателей на функции, указывающих на джаваскриптовые функции.

(Заметим, что эта часть библиотеки ещё не готова, но над патчем идёт работа.)
// Определим тип функции сравнения, которая принимает два указателя
// на элементы, а затем возвращает:
// -1, если i < j;
// 0, если i == j;
// 1, если i > j.
// Эквивалентный Си-код таков:
//  typedef int (comparator_t*)(const int8_t* i, const int8_t* j);
let comparator_t = ctypes.FunctionType(ctypes.default_abi, ctypes.int,
                                      ctypes.int8_t.ptr, ctypes.int8_t.ptr);

// Каков Си-тип 'comparator_t'?
comparator_t.name;
// выдаст "int (*)(int8_t*,int8_t*)"

// Определим функцию 'qsort', которая принимает массив элементов,
// и функцию сравнения, и сортирует массив.
//  void qsort(void* array, size_t length, size_t elemsize, comparator_t comp);
let qsort = library.declare("qsort", ctypes.default_abi, ctypes.void_t,
             ctypes.voidptr_t, ctypes.size_t, ctypes.size_t, comparator_t);

// Реализуем джваскриптовую функцию, точно соответствующую вышеприведённому 'comparator_t'.
function reverse(i, j) { return j.contents - i.contents; }

// Создадим сишный указатель на функцию, указывающий на нашу джаваскриптовую функцию.
let reverse_ptr = comparator_t(reverse);

// Что такое 'reverse_ptr'?
reverse_ptr.toString();
// выдаст "ctypes.FunctionType(ctypes.default_abi, ctypes.int, ctypes.int8_t.ptr,
//                             ctypes.int8_t.ptr)(ctypes.UInt64("0x81a430cb"))"

// Создадим массив значений и вызовем 'qsort'.
let array_t = ctypes.int8_t.array();
let ints = array_t([3, 1, 5, 6, 4, 2]);
qsort(ints.address(), ints.length, array_t.elementType.size, reverse_ptr);

// Вуаля!
ints.toString();
// выдаст "ctypes.int8_t.array(6)([6, 5, 4, 3, 2, 1])"


* This source code was highlighted with Source Code Highlighter.

Итак, если вы автор расширения, или пишете код для браузера, имейте в виду js-ctypes — и дайте нам знать, как пойдут дела!
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 41

    +1
    а как это расширение попадет в браузер? И что будет с security (риторический вопрос)?
      +1
      Как явствует из первого примера, библиотека js-ctypes будет доступна каждому такому расширению, которое подгрузит её с адреса «resource://gre/modules/ctypes.jsm» (адрес этот локальный).

      Безопасность будет достигнута за счёт того, что эта библиотека доступна только для расширений (которые и без неё могут сделать всё что угодно), а не для сайтов большой и страшной Паутины.
        –2
        Ну мне кажется очень сложно будет такое применить безопасно)
          +2
          Вам кажется :)
            +4
            Минус-то обоснуйте. Один бес, расширения могут творить что угодно, а кроме как им, js-ctypes больше ничему не доступен. Ну и где тогда проблема?
              0
              Расширения могут взаимодействовать с открытыми страницами. Были уже уязвимости в расширениях, с этим связанные. (В том числе в грязной обезьяне и фаербаге с выполнением любого кода в контексте расширения).

              Например, расширение берёт какую-нибудь строку с открытой страницы и передаёт её в функцию WinAPI, которой надо явно указывать длину, и в результате переполнение.
                0
                Я не минусовал) Увидим скоро, как окажется на самом деле) возможно вы и правы)
              +1
              Прочитайте ещё раз, на что вы отвечали.

              Плагины и сейчас могут всё.
          +6
          Местный код — это пздц. Написал бы хотя бы «родной».
            0
            Расширению он не родной совсем.
              +7
              Ну и ни разу не «местный».
              Мицгол, спасибо тебе за перевод, но использование слова «местный» в значении «нативный» не красит ни тебя, ни твой текст. И не делает обоих русскими.
              –3
              +1, торжество надмозгов.
              0
              Я что-то в тексте ни одной ссылки не могу найти. Где встречать-то?
                +1
                Видимо в 3.7 ее можно загрузить вот так: resource://gre/modules/ctypes.jsm
                Вообще, идея хорошая, с ее помощью можно будет еще больше частей Firefox'а переписать на Javascript.
                  0
                  Плохая идея, негодная. С её помощью родится куча расширений, привязанных к операционной системе.
                    0
                    Уже сейчас можно запросто написать расширение, которое будет привязано к системе.
                    Разработка расширений для Firefox уже сейчас — рокет сайнс, еще больше ее усложнить уже кажется нереально, поэтому еще один стотысячный путь сделать что-то неправильно вряд ли усугубит ситуацию.
                      0
                      Насчёт запросто — не знаю. Каким образом? Привязаться к системным путям?
                        0
                        Коллега, у нас процентов 20 ошибок связаны с работой расширения на разных ОС. Пути и методы их формирования, конечно, первый путь, но обычно ошибки не так очевидны, связаны с различиями в управлении окнами, фокусом, скинами, выводом графики и пр.
                          0
                          Понятненько. Ну, теперь этот процент будет выше :)
                –1
                Привет ActiveX!
                  +2
                  А вот ActiveX тут совсем непричем.
                    –2
                    В 1996-м году ActiveX тоже был секьюрным и всячески волшебным. А спустя 5 лет вы сами знаете что случилось. Сейчас FF займёт ещё 10% рынка и мы поимеем все «прелести» популярности. Только в этот раз счастье будет не только у пользователей Windows, но и у пользователей Linux и MacOS X.

                    И не надо рассказывать про то, что без рута ничего сделать нельзя — качать порно-траффик можно и под обычным юзером.
                      +3
                      Вы путаете.
                      ActiveX — решение контекста пользовательской страницы. Но оно даже проектировалось не как шибко безопасное.
                      Firefox extension — решения контекста браузера.
                      Любой расширение для Firefox абсолютно не безопасно (об этом вас четко предупреждают при его установке), оно может выполнить абсолютно любое действие с правами самого браузера. Более того, расширение может ненароком добавить в ваш браузер уязвимость, которая позволит сторонним веб-страницам получить эти же самые права.
                        –2
                        Я не путаю. Уже были наработки на тему несанкционированной скрытой установки расширений. Учитывая возможности экстеншенов такая установка может выпонить заливку какого-то кода на комп и удаление расширения. В итоге пользователь вообще ничего не заметит.
                          +2
                          Может быть вы и не путаете, но логики в ваших словах точно не много. Перечитайте весь тред и обоснуйте ваши соображения об ущербе безопасности еще раз подробно.
                            –2
                            Расширения — изначально большая дырка. Теперь «портировать» известные мальвари будет ещё проще. А так ничего особенно не поменяется.
                              0
                              Я воздержусь от оценки безопасности Firefox'а.
                              Но если представить гипотетическую угрозу, то сегодня на XPCOM было бы весьма удобно писать кросс-платфоменное «мальваре».
                                –1
                                Тут уже исключительно вопрос рыночной доли. С выходом IE8 + Win7 ломать IE стало настолько сложно (не невозможно, но экономически нецелесообразно), что сейчас все силы идут на эксплойты продуктов Adobe.
                  +1
                  Весьма и весьма занятно…
                    0
                    Вообще-то в 3.6 уже есть этот модуль.
                    resource://gre/modules/ctypes.jsm
                    let EXPORTED_SYMBOLS = [ "ctypes" ];
                    const init = Components.classes["@mozilla.org/jsctypes;1"].createInstance();
                    init();
                    
                      0
                      А использовать получается? В браузерах встречается функционал, который, вроде, есть, но не анонсирован и не работает.
                        –1
                        Возможно, ещё не настолько навороченный? (А иначе чего это тот блоггер, которого я перевёл, говорит именно о следующей версии Файерфокса?)
                          –1
                          Опять же и на MDC пишут, что Firefox 3.7.
                        +1
                        Я человек далёкий от программирования, и смысл статьи, к сожалению, почти не уловил.
                        Возможно ли, с помощью описанного решения, подключить к лисе, например, библиотеки кодека H.264 и заставить лису при проигрывании ролика кодированного этим кодеком отображать его через тег <vidео> описанный в HTML 5? Просто временный костыль, до которого я сумел додуматься — довольно убогое решение, до которого я пока сумел додуматься.
                          0
                          Нет, потому что тег <video> не настолько скриптуется.

                          Можно было бы расширением заменять его на <object> для использования плагина.

                          Но тут js-ctypes не нужен.
                          0
                          ой мне кажется не туда они полезли… лучше бы увеличили нормальное апи для базовых функций
                            –1
                            Ну, есть же FUEL.
                            +2
                            Еще примеры можно найти на сайте MDC.
                              +1
                              эм… хабрапарсер как всегда молодец :(

                              clck.ru/0B2D
                              –2
                              Постскриптум.   Возможен вызов библиотеки с подбором расширения в зависимости от платформы. Согласно комментарию 4 к багу 518130, вот этот вызов:
                              var name = ctypes.libraryName("foo");
                              возвращает «foo.dll», или «libfoo.so», или «libfoo.dylib» (в зависимости от платформы). Он реализован в баге 577589.
                                –2
                                Постскриптум.   Пособие по заданию типов для js-ctypes: «Declaring types».

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое