Архитектура Skype изнутри и его транспортные протоколы

Небольшое замечание. Я знаком со всеми докладами по анализу протокола Skype. Знаю о skypeopensource, знаю о проекте француза FakeSkype и т.д.

Подходы мне не понравились.

Какие-то данные
Какие-то результаты
Где-то что-то
Как-то отправить и что-то получить
Это не мой путь

Для меня реверс инжиниринг — это однозначный ответ на вопрос, а не угадывание каких-то значений или изучение сетевых пакетов. Поэтому предлагаю другой взгляд и анализ. Расскажу, как я прошел его с самого начала. В статье не будет исходников и не будет полного описание протокола. Так же я не буду ни опровергать, ни подтверждать схожую информацию с других источников. Для себя я смог полностью разобрать транспортный сетевой уровень Skype и криптографию, но публиковать не буду по соответствующим соображениям.

Итак, свой реверс инжиниринг я начал не с загрузки последней версии Skype в дизассемблер или отладчик, а с поиска и изучения его первых версий и истории создания. Воспользовавшись web.archve.org, заглянул на сайт skype.com в прошлом, удалось скачать почти первую и очень старую версию 0.9.x. skype, в последствии для изучения были скачаны все доступные версии под все платформы в веб-архиве. Не забыл и о skypekit sdk window/linux, которым делились добрые люди, начиная от старых 2.0 версии, включая последние доступные.

При изучении истории Skype часто упоминалась компания joltid ltd. Ну что же, вбив адрес в web.arhive.org, изучил и скачал все с раздела download. Но история ребят, которые создали Skype, говорит, что одним из первых их продуктом была программа KaZaA, которая позволяла в сети p2p обмениваться файлами. Существовал так же открытый плагин-клиент giFT-FastTrack для сети KaZaA, который в далеком ~2000 году удалось создать энтузиастам, используя реверс инжиниринг. Он легко ищется в google, поэтому скачал и его.

Так же в поле зрения попал один из проектов skype — anthill, в последствии переименовавшийся в joost. Это был online p2p tv, который не набрал популярности и прекратил существование. Но меня интересовала не его история, а, конечно же, софт. С помощью web.arhive.org и google были найдены некоторые программы-плагины под win и macosx.

Не обошли внимание и модули SFA(Skype For Asterisk).

Ну, что же, вся информация собрана, буду анализировать.

Известно, что Skype тщательно шифровала свои продукты. Используя мемори дамперы, с легкостью снимаем защиту. Запускать я ничего не собираюсь, а для статического анализа в IDA этого достаточно.

В эволюции шифрования своих бинарников skype изменилась лишь слегка, а именно — если где-то до второй версии Skype был однообразный ксор рекурсивный проход, то где-то после второй добавили еще и упаковку.

Дальнейшее изучения перешло в хекс просмотрщик. Что в нём можно изучать, спросите вы? Конечно же, текстовые строки, которые содержатся в программах. В некоторых современных skype клиентах обфусцированы даже они. Но в старых версиях бинарок обнаружилась не только чистые строки, но и не отключенная rtti информация о С++ классах. Да-да, ядро skype написано целиком на С++. Это позволило выстроить взаимосвязь отдельных модулей по их названию. Оставленная информация в текстовых отладочных сообщениях тоже сильно упрощает весь процесс реверс-инжиниринга.

Изучая версию за версией бинарок skype, хотелось найти такой бинарный модуль, где оптимизатор поработал меньше всего, чтобы даже inline функции, которые использовались в исходном коде, не развернулись бы. И такие бинарники были найдены, и не один.

Итак, вся архитектура skype — это модули, функции которых выполняются в основном потоке класса Backbone. Сам класс Backbone наследуется от класса Thread. В самом Backbone объявлен интерфейс класса Module, который имеет с десяток обработчиков для обработки рассылок каких то данных.

В итоге выстраивается такая архитектура, покажу очень упрощенно.

псевдокод
class Thread
{
	virtual void Run() = 0;
};

class Backbone : public Thread
{
	class Module
	{
		virtual void handler_1(void *ev) {};
		virtual void handler_2(void *ev) {};
		virtual void handler_3(void *ev) {};
		virtual void handler_4(void *ev) {};
		....

		void registerModule(Backbone *);
		void unregisterModule();

		void addCallbackFunction(void (Module::*)());
		
	};

	list<Module*> ModuleList;

	void AddModule(Module *m) { ModuleList.add(m); }

	void Notify_1(void *ev)	{ for (i=0;i<ModuleList.size();++i) ModuleList[i].handler_1(ev); }
	void Notify_2(void *ev)	{ for (i=0;i<ModuleList.size();++i) ModuleList[i].handler_2(ev); }
	void Notify_3(void *ev)	{ for (i=0;i<ModuleList.size();++i) ModuleList[i].handler_3(ev); }
	void Notify_4(void *ev)	{ for (i=0;i<ModuleList.size();++i) ModuleList[i].handler_4(ev); }
	...

	void Run()
	{
		while(!Quit)
		{
			//цикл обработки всех callback функций модулей в списке

			//пулинг сетевой системы, прием данных в буферы с сокетов,
			//	 отправка данных с буфера в сокеты.
		}
	}
};


Изучение текстовых строк в Skype и других его проектах показало, что имя папочки Joltid непосредственно фигурирует в проектах Skype, но некоторые остальные файлы вынесены «за скобки». В проекте Anthill(Joost) папочка Joltid вынесена на уровень выше.

«c:\slave1\win-production\anthill\joltid\gi\general\backbone\InternetConnection.hpp»
«c:\slave1\win-production\anthill\anthill\transceiver/transceiver.hpp»
«c:\slave1\win-production\anthill\anthill\..\joltid\GI\General\Backbone/Backbone.hpp»

А наследование классов в skype и anthill идет от класса Backbone. Значит, делаю предположение, что есть какая-то общая технология, которую используют исследуемые продукты.

В конструкторе класса Backbone можно обнаружить много модулей, которые осуществляют свою регистрацию.

псевдокод
	class APIEventManager : public Backbone::Module
	{
		...
	};

	class Router : public Backbone::Module
	{
		...
	};

	class InternetConnection : public Backbone::Module
	{
		...
	};

	class FallbackConnManager : public Backbone::Module
	{
		...
	};

	class RelayConnectionManager : public Backbone::Module
	{
		...
	};

	Backbone::Backbone()
	{
		AddModule(new APIEventManager);
		AddModule(new Router);
		AddModule(new InternetConnection);
		AddModule(new FallbackConnManager);
		AddModule(new RelayConnectionManager);

		... итд весь список публиковать не буду
	};


Похоже, модули в конструкторе и создают саму технологию Joltid, которая позволяет выстроить p2p-распределенную сеть для передачи любого вида цифровых данных. А унаследовав Backbone, можно разрабатывать любые продукты, которые используют p2p построение сети.

В проекте anthill в первых версиях в классе наследнике добавляется всего один модуль Transmitter. Который по всей видимости отвечает за трансляцию и прием медиа данных. А вот в проекте Skype в классе наследнике SkyBackbone модулей около десятка. А в новых версиях Skype и того больше. Логично, у Skype функциональность побольше, Авторизация, Чат, Пересылка файлов и т.д. Но меня интересовало самая основа Skype, его взаимодействие с сетью и криптография.

Изучая в хекс редакторе во всех проектах просматривался некий модуль CommLayer, его сообщений вместе с ключевыми словами TCP и UDP было больше всего.

«CommLayer: Deleting packet #%04x»
«CommLayer: Ack to #%04x»
«CommLayer: Sending user packet #%u over TCP #%u. len=%u»
«CommLayer: %sending packet #%04x to %s using TCP»
«CommLayer: %sending packet #%04x to %s using UDP»


Значит, этот модуль как-то отвечает или взаимодействует с сетью.

Анализируя наследие классов в оставленном rtti, получил некую структуру:

class CommLayer
{
	class ConnectionListener;
	class CommandListener;
	class Transport;
	class TCPConnection;
	class UDPTransport;
};

Так же в rtti были найдены классы для работы с сетью, class Connection, class UDPConnection, которые при изучении наследования подсказали, что наследники ведут всего в два класса.

Первый — это CommLayer. А второй — FELayer, который, кстати, не входит в модуль Backbone. Значит, не является основой p2p сети skype, но тем не менее для чего то нужен. Отлично, посмотрим в rtti и на него:

class FELayer
{
	class PacketListener;
	class ConnectionListener;
	class Conn;
};

Значит, есть как минимум два класса для работы с сетевой частью. Осталось разобраться с ними, какие данные они передают, какие принимают. И где же наконец-то используется всеми известная криптография rc4/aes/rsa/dh.

Грузим в IDA поочередно разные бинанарки skype и его продуктов и изучаем. Идентифицируем общеизвестные алгоритмы md5/sha1/sha2/aes, помечаем функции. А вот rsa не видно, но не беда, rsa строится на bignum. Более пристальный анализ
и можно с легкостью опознать bignum, а с ним и остальной rsa. У Skype своя реализация bignum/rsa.

Как оказалось, Diffie-Hellman в первых версиях Skype не было, его добавили чуть позже, где-то после 1.3.x версии Skype.

Анализируя бинарки часто встречается число 69069 (0x10dcd), используя гугл находим — это random, а вот смещение "+17009" (0x4271) немного не стандартное, но не беда. Рандом так рандом. Так же эти константы обнаружились и в giFT-FastTrack.

static unsigned int seed_step (unsigned int seed) {
        return 0x10dcd * seed + 0x4271;
}

Можно сказать что данный рандом уникальный для всех skype продуктов ( joltid ?)

Особое внимание обращает на себя какая-то странная упаковка чисел. При изучении была найдена в sdk skypekit.

BinProtocolClientDecoder.java:

public int decodeUint() throws IOException {
        int shift = 0;
        int result = 0;
        while (true) {
                int value = mTransport.readByte() & 0xFF;
                result = result | ((value & 0x7f) << shift);
                shift = shift + 7;
                if ((value & 0x80) == 0)
                        break;
        }
        return result;
}

BinProtocolClientEncoder.java:

public Encoding encodeUint(final int value) throws IOException {
        int v = value;
        while (v > 0x7f) {
                transport.writeByte((byte)(0x80|(v&0x7f)));
                v = v >> 7;
        }
        transport.writeByte((byte)v);
        return this;
}


в том же sdk аналогичный на С++
SidProtocolBinCommon.cpp
Status BinCommon::wr_value(CommandInitiator* thread, const uint& val)
{
//printf("encoding %d %x\n", val, val);
  uint value = val;
  int  sz = 0;
  char buf[(32+7)/7];
  for(;value>0x7f;value>>=7)
    buf[sz++] = (char)((value&0x7f)|0x80);
  buf[sz++]=(char)value;
  return (Status) m_transport->bl_write_bytes(thread, sz, &buf[0]);
}


Status BinCommon::rd_value(CommandInitiator* thread, uint64& val)
{
  val=0;
  uchar c;
  uint shift=0;
  do {
    if (rd_uchar(thread, c) != OK) return ERR_DECODE;
    val|=((uint64)c&0x7f)<<shift;
    shift+=7;
    // needed ?
    if (shift > 64 && c&0xfe) return ERR_DECODE;
  } while(c&0x80);
  return OK;
}



Так же встречается очень часто числа 40503 и 2654418637 (0x9e3736cd) вместе с xor и смещениями *4.

код из IDA
int key2index(int a1, int a2) {  return (40503 * ((*a2 >> 16) ^ *a2)) >> 8; }

sint hash_remove(int this, int value)
{
  int p0; // edx@1
  int p; // eax@1
  signed int result; // eax@4
  int v5; // ecx@6

  p0 = this
     + 4
     * ((unsigned __int16)(40503
                         * (((unsigned int)(*(_DWORD *)(value + 4) ^ *(_DWORD *)(value + 8)) >> 16) 
				^ *(_WORD *)(value + 4) ^ *(_WORD *)(value + 8))) >> 8);
  p = *(_DWORD *)p0;
  if ( *(_DWORD *)p0 )
  {
    while ( p != value )
    {
      p0 = p;
      p = *(_DWORD *)p;
      if ( !p )
        goto _return_0_loc_81D47CC;
    }
    --*(_DWORD *)(this + 0x400);
    v5 = *(_DWORD *)p;
    result = 1;
    *(_DWORD *)p0 = v5;
  }
  else
  {
_return_0_loc_81D47CC:
    result = 0;
  }
  return result;
}

uint __usercall sub_find<eax>(int *a1<eax>, int a2<edx>)
{
  int v2; // esi@1
  int v3; // edi@1
  uint result; // eax@1
  int i; // ecx@1

  v2 = *(_DWORD *)(a2 + 48);
  v3 = *a1;
  result = (uint)(2654418637 * *a1) >> *(_DWORD *)(a2 + 36);
  for ( i = *(_DWORD *)(v2 + 4 * result); i != -1; i = *(_DWORD *)(v2 + 4 * result) )
  {
    if ( *(_DWORD *)(*(_DWORD *)(a2 + 16) + 12 * i) == v3 )
      break;
    if ( (signed int)++result >= *(_DWORD *)(a2 + 32) )
      result = 0;
  }
  return result;
}



Cудя по поиску в Google, числа являются частью алгоритмов для построение хеш таблиц:

/* 16-bit index */
typedef unsigned short int HashIndexType;
static const HashIndexType K = 40503;

/* 32-bit index */
typedef unsigned long int HashIndexType;
static const HashIndexType K = 2654435769 (0x9e3779b9);


32 битный index немного отличается от используемого в Skype, но заострять свое внимание я на этом не стал.

Немного пропустим сам процесс дальнейшего разбора бинарников, перейдем к результату.

Разобрав всю логику транспортного уровня, а так же немного выше, оказалось, что Skype внутри использует примитив, который называется Attribute. И существует контейнер для хранение списка атрибутов. Сам атрибут может представлять
из себя один из простых типов.

enum
{
	e_Integer, // = 0	32 битное число
	e_Integer64, // = 1	64 битное большое число
	e_Address, // = 2	айпи адрес 32 число +порт 16 число
	e_String, // = 3	строка, Н количества байт заканчивающаяся нулем
	e_Rawdata, // = 4	бинарные данные
	e_Container, // = 5	вложенный контейнер
	e_IntegerArray, // = 6	массив 32 битных чисел
};

В контейнере осуществляется сериализация/десериализация атрибутов. Большинство целых чисел упаковываются/распаковываются по алгоритму упаковки чисел, описанных выше encodeUint/decodeUint. Остальные данные копируются как есть.

Такой тип контейнера видимо занимал большой размер на выходе после сериализации и спустя 0.9 версии Skype уже в 1.0.x версии был добавлен режим упаковки атрибутов вместо сериализации. Упаковка представляет из себя обычный range_coder арифметическое кодирование. Где алгоритм скопирован один в один по ссылке, за исключением того, что частоты для кодера забиты в Skype статически.

encoderRenormalize, код из IDA
void encoderRenormalize(range_coder *this)
{
  unsigned int _valfield_4; // edx@2
  unsigned int _infield_8; // ecx@7
  char countv3; // zf@9

  if ( this->Range <= 0x800000 )
  {
    do
    {
      _valfield_4 = this->Low;
      if ( _valfield_4 > ~0x80800000 )
      {
        if ( (signed int)_valfield_4 >= 0 )
        {
          ++this->Help;
          goto LABEL_7;
        }
        this->vtbl->output(this, this->Buffer + 1);
        if ( this->Help )
        {
          do
          {
            this->vtbl->output(this, 0);
            countv3 = this->Help-- == 1;
          }
          while ( !countv3 );
        }
      }
      else
      {
        if ( this->Start )
          this->Start = 0;
        else
          this->vtbl->output(this, this->Buffer);
        if ( this->Help )
        {
          do
          {
            this->vtbl->output(this, 255u);
            countv3 = this->Help-- == 1;
          }
          while ( !countv3 );
        }
      }
      _valfield_4 = this->Low;
      this->Buffer = this->Low >> 23;
LABEL_7:
      _infield_8 = this->Range;
      this->Low = (_valfield_4 << 8) & ~0x80000000;
      _infield_8 <<= 8;
      this->Range = _infield_8;
    }
    while ( _infield_8 <= 0x800000 );
  }
}


decoderRenormalize, код из IDA
void decoderRenormalize(range_roder *this)
{
  _range_roder_vtbl *v1; // eax@3
  unsigned __int8 value; // al@3
  unsigned int v3; // edx@3

  if ( this->Range <= 0x800000 )
  {
    do
    {
      v1 = this->vtbl;
      this->Low = (unsigned __int8)(this->Buffer << 7) | (this->Low << 8);
      value = v1->input((int)this);
      this->Buffer = value;
      this->Low |= value >> 1;
      v3 = this->Range << 8;
      this->Range = v3;
    }
    while ( v3 <= 0x800000 );
  }
}


Разбор FELayer показал, этот layer использует только TCP для взаимодействия. Одно из назначения данного модуля — это обеспечение транспортного уровня:
— при регистрация или входа в сеть skype;
— звонках (выполняет сигнализацию, так же как SIP или h323 в voip);
— пересылке файлов;
— и в первых версиях skype передача аудио over TCP?

Формат данных такой:

struct fe_header
{
	byte type;
	byte v_hi; // всегда = 3
	byte v_lo; // всегда = 1
	word len;
};

fe_header + опционально сереализированые атрибуты + опционально пользовательские данные.

Те данные, что помечены опционально, могут опционально сверху шифроваться aes и rc4. Layer пытается строить из себя некий SSL, хотя таковым, конечно, не является.

«FELayer: TCP: %s Received packet with unknown SSL version (%u.%u)\n»


Обмен rsa ключами aes salt + дополнительная информация передается в атрибутах, помеченных опционально. При разборе обнаружено четыре типа пакетов для данного layer-a. Это type=20, type=21, type=22 и type=23.

type=20 — это userpacket c aes256, устарел, больше не используется.
type=21 — это userpacket без aes256, устарел, в первых версиях Skype использовался для передачи RTP.

Несмотря на то, что пакеты этого типа не используются, функционал приема и разбора этих типов оставлен в обработчике FELayer-а.

type=22
как ping — с одним лишь заголовком, без данных
и как handsnake — с добавленными атрибутами(8,9,11,12) в которых содержатся данные rsapub ключей,
salt и доп инфо

type=23
как keepalive — с заголовком, без данных.
и как datapacket — если в себе содержит еще и данные, внутри которых данные модулей верхних уровней.

На этапе первичного TCP соединения происходит обмен ключами по Diffie-Hellman алгоритму. В дальнейшем ключ будет использоваться в rc4 алгоритме шифрования.

Существует ситуация, когда обмен ключами Diffie-Hellman может закончиться неудачей, в таком случае rc4 для шифрования использоваться не будет. Не беда, есть еще aes.

Сам aes почти стандартный обычный aes256 ctr/cm, где index для него является uint64 разрядным.

код из IDA
void userpacket_crypto_encrypt(userpacket_crypto *this, uchar *ptr, uint len, uint64 index, uint nullkey, uint salt)
{
  uint count; // esi@3
  uchar *pdata; // ebx@5
  uint _j_; // edi@7
  uint seq; // [sp+18h] [bp-134h]@6
  uchar *aesks; // [sp+1Ch] [bp-130h]@1
  uint block[4]; // [sp+20h] [bp-12Ch]@7
  uchar zerov11[32]; // [sp+30h] [bp-11Ch]@2
  uchar keyv15[240]; // [sp+50h] [bp-FCh]@2

  aesks = this->Key;
  if (nullkey)
  {
    memset(zerov11, 0, sizeof(zerov11));
    rijndaelKeySched(zerov11, keyv15);
    salt = 0;
    aesks = keyv15;
  }

  count = len;
  if (len > 1048576)
    count = 1048576;
  pdata = ptr;
  if (count)
  {
    seq = 0;
_loop2_aes_loc:
    block[0] = salt;
    block[1] = salt;
    block[2] = index >> 16;
    block[3] = ((_DWORD)index << 16) + seq;
    rijndaelEncrypt((int *)block, (int *)aesks);
    _j_ = 0;
    while (_j_ != count)
    {
      pdata[_j_] ^= block[_j_ >> 2] >> (24 - 8 * (_j_ & 3));
      if (++_j_ > 15)
      {
        ++seq;
        count -= 16;
        pdata += 16;
        goto _loop2_aes_loc;
      }
    }
  }
}


В конце данных после шифрования дописывается слово(word) два байта. Которое является результатом xor двух слов(word) crc32(данных) ^ index.

код из IDA
void userpacket_crypto_finish(uint64 index, char *ptr, int len)
{
  int res; // r4@1
  int crc32; // [sp+4h] [bp-20h]@1

  crc32 = -1;
  CRC32(&crc32, ptr, len);
  res = (index ^ crc32) & 0xFFFF;
  ptr[len] = res & 0xffff;
  ptr[len + 1] = res >> 8;
}


Вот как? Интересно, как же index восстанавливается на принимающей стороне? Немного разобрав алго, видно, что индекс на передающей стороне имеет разрядность uint64, но увеличивается только до 48 бит, index & 0xffffffffffff.

код из IDA
uint64 userpacket_crypto_get_send_index(userpacket_crypto *this)
{
  uint64 res; // r8@1

  res = this->SendIndex;
  this->SendIndex = res & 0xFFFFFFFFFFFFLL;
  return res;
}


А передается только младшая часть 16 бит 0xffff, исходя их функции userpacket_crypt_finish.

Оказывается, на принимающей стороне index восстанавливается опять до uint64 по известному алгоритму, который используется в srtp — смотрите «Algorithm 1».

Код взят из листинга IDA, очевидно, поработал оптимизатор компилятора.

код из IDA
uint64 userpacket_crypto_recv_index(unsigned short in_index)
{
	if (LoIndex & 32768)
	{
		if ((LoIndex - 32768) <= in_index && LoIndex >= in_index)
			return in_index + (HiIndex<<16);

		HiIndex= (HiIndex >= -1) ? 0 : HiIndex+1;
		LoIndex = in_index;

		return in_index + (HiIndex<<16);
	}

	if (in_index <= LoIndex || (in_index - LoIndex) <= 32768)
	{
		if (LoIndex < in_index) LoIndex = in_index;
		return in_index + (HiIndex << 16);
	}

	return in_index + (( (HiIV-1)>>32|(HiIV-1) )<<16);
}


Весь этот класс объекта для работы с aes256 называется userpacket_crypto. В него так же входит процедура генерации ключа, и некоторые другие функци криптографии.

Теперь рассмотрим CommLayer. Как оказалось, после разбора этот леер работает и поверх TCP и поверх UDP. В TCP транспорте на этапе соединения он как и FELayer использует алгоритм Diffie-Hellman, ключи которого будут использоваться… в rc4 обфускаторе!

Но об этом позже.

Назначений у CommLayer несколько:
— обмен командами;
— обмен userpacket_crypto;
— обмен udp raw данными в обход всего CommLayer.

Так же CommLayer имеет свой уровень транзакции. Номер отправляемого, принимаемого пакета.

Команды CommLayer представляют из себя некоторый мини заголовок с нескольких байт. В которых кодируется тип команды + опционально SeqNum, дальше команда дополняется сериализироваными атрибутами с контейнера. Команды могут быть с подтверждением доставки. Без подтверждений, Ответами на запрос, и т.д. CommLayer один, но используется многими модулями.

Как же команды определяются, куда какому модулю? Это отдельной процедурой регистрации команды на каждый модуль. Да-да, каждый модуль может зарегистрировать свои номера команд в общем CommLayer, а выставляя SeqNum в отправляемой команде можно эмулировать некоторый режим транзакции. А самих модулей, использующих команды, очень много — Localnode, Firewall, replay_manager_t, BroadcastChannelManager и т.д.

Так же в отправляемой команде в CommLayer в очередь на отправку можно:
— задать время таймаута;
— сетевой адрес;
— транспорт тип UDP/TCP;
— подписать свой ReplyListener callback класс, который сработает, если команда ожидает ответа.
И так далее.

В чем различия ReplyListener от процедуры регистрации команды в CommLayer? В том, что регистрацию создает CommanListener, он только ожидает прием команд и может отвечать, а ReplyListener получает ответы на свои запросы. Вроде разобрались.

Обмен userpacket_crypto — это все тот же рассмотренный aes256 ctr/cm c uint64 индексом. Он используется в session_manager_t для отправки своих команд, которые тоже имеют свой layer упаковки и транзакций. Туда входит построение релей соединений, обмен сообщениями(чат), и т.д.

Для обмена RTP Audio/Video, в отличии от первых версий, Skype изменил подход. В первых версиях, когда еще не было видео, но был звук, RTP передавался в userpacket_crypto, добавляя заголовок самого CommLayer для идентификации пакетов. Так же RTP могло передаваться и через FELayer поверх TCP. Позже был изменен этот алгоритм и RTP Audio/Video принимаются и передаются сразу в UDP сокет. Да, все так же шифруются в userpacket_crypto, но не добавляется CommLayer заголовок. По видимому, это создавало накладные расходы на при очень большом потоке данных, отслеживая еще и транзакции самого CommLayer. А в последующий версиях к RTP был добавлен FEC.

Основные типы пакетов в CommLayer TCP, session_header, ack, end_session, keepalive, req_obfuscation UDP транспорт приблизительно такой же, как и TCP, за исключением того, что там не используется Diffie-Hellman и добавлена возможность отправки больших пакетов фрагментами. При не получении одного из фрагментов протокол запросит не дошедшие пакеты повторно. Так же CommLayer умеет менять на лету режим обфускации.

Обфускация в Skype — это обычный RC4 алгоритм, но с замусоренной перестановкой ключа до его установки в RC4.

код из IDA
void  Obfuscator_Init(int this, uint seed, uint mode, uchar *keystr, uint keylen)
{
  uint i; // ecx@9
  uint v8; // dl@11
  uint v9; // al@11
  uchar key[80]; // [sp+14h] [bp-64h]@4
  int v11; // [sp+64h] [bp-14h]@5
  int v12; // [sp+68h] [bp-10h]@5

  if (!mode)
    mode = 1;
  i = 0;
  do
    *&key[4 * i++] = seed;
  while (i != 20);

  if (mode_& 1) Obf_0(key, seed);
  if (mode & 2) Obf_1(key, seed);

  for (i = 0; ; key[i - 1] ^= keystr[i-1])
  {
    v8 = i <= 79;
    v9 = i++ < keylen;
    if (!(v8 & v9))
      break;
  }
  ARC4::SetKey(this, key, 80);
}


Обфускатор находится в функциях Obf_0, Obf_1. Сами функции из себя представляют побитовые сдвиги, xor-ы и в разных вариациях. Так же их код по возможности запутан, чтобы его невозможно было разобрать даже используя реверс-инжиниринг.

Основные данные для обфускатора — это seed, режим и ключ, полученный от Diffie-Hellman. Для поддержки обфускации в протоколе CommLayer оставлено двойное слово 0xffffffff, каждый бит — это один из режимов. Итого, обфускация допускает без изменения протокола Skype до 32 режимов в разных вариациях смешивания. На практике больше 2 режимов не используется.

В некоторых Skype модулях удалось обнаружить 4 режима обфускации. Это 0xf байт, но в большинстве это 0x3 значение. Т.е. не больше 2 режимов. После инициализации в протоколе CommLayer пиры обмениваются запрос-ответами, чтобы договорится, в каком режиме обфускации будут осуществлять обмен. Каждый из пиров отправляет SupportedMode и RequiredMode.

SupportedMode определяет константы, которые раньше в Skype были статической переменной и легко находились ссылкам в дизассемблере, о том, в какой именно функции используется данная константа. В современном Skype то ли оптимизатор компилятора пошалил, то ли константа замена на define SupportedMode, тем самым превратившись в обычное число в
дизассемблере. Найти все ссылки не так просто, если до этого не знать всех мест размещения. Соответственно SupportedMode показывает, с каким количеством обфускаций скомпилирован обфускатор.

Эволюция skype


Периодически посматриваю внутрь Skype. С 2003 года очень много чего изменилось, но транспортный FELayer и CommLayer остались почти такими, как и прежде. Многие модули так же не очень сильно изменились, зато мутация кода выросла, появилось много абстрактных интерфейсов, что породило множество виртуальных функций. Претерпел изменения сам Backbone, обработку модулей переносили то туда, то сюда. В итоге был создан модуль BareBackbone, в котором осуществляется обработка модулей. Из главного потока Backbone она была вынесена, видимо, сказывалась латентность главного потока, который еще и сетевой частью занимается. Оптимизируются aes/rc4/bignum алгоритмы.

Изучая найденные клиенты KaZaA и скачанные модули с сайта Joltid, удалось понять и увидеть глазами реверс инженера с чего начинался Skype. Cвоя реализация bignum, rsa, обфускация rc4, транспортный уровень работы сетью, хеши, списки и т.д. Все очень по простому и компактно. Сейчас таких программ нет, обилие boost, stl, openssl, программы превращаются
в монстров.

Изучая gift-FastTracker, оказалось что ребята в лихие 2000 смогли разреверсить rsa-bignum от KaZaA (использующийся и в Skype), тогда, правда, он был попроще и у них не было современного арсенала ida+hexrays. Если сравнивать, то современный bignum от Skype сильно оброс оптимизациями под разные платформы. Так же ребятам из FastTrack удалось разреверсить 4 режима обфускации.

Заключение


Наверное, если снять эти два транспортных уровня, то Skype уже не будет выглядеть как Skype. Более скучен для секьюрити ресерча, менее криптографичен. Поэтому, как минимум, этот сетевой слой делает Skype таким уникальным, какой он есть.

При реверс инжиниринге не использовался отладчик. Я ни разу не запускал Skype для отладки и не снимал дампы пакетов для анализа. Все было сделано статическим анализом ida+hexrays. И это основное отличие от проведенных до этого анализов skype-протокола, которое, я считаю, дает больше информации в понимании как устроена сетевая часть.
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 38
  • +10
    Божественная работа!
    • 0
      Даже не думал что кому то будет интересно. Цели преследовались другие.
    • +16
      А была ли осуществлена практическая проверка корректности RE в виде подключения к сети скайпа?
    • 0
      Помогите понять: с появлением Skype for Web смысла в реверс инжениринге бинарников уже нет, можно же разобрать JS и использовать эту точку входа, ведь так?
      • +4
        Skype4Web != Skype. это просто вебморда над клиентом. не имеет никакого отношения к протоколу скайпа, и не дает всех его возможностей.
        • +2
          Там еще бинарный плагин к браузеру.
          • 0
            Кстати да, его забыл упомянуть. Как раз в начале недели смотрел этот плагин.
        • +3
          После того, как скайп попал в руки MS особого смысла в нем тоже нету. Потому что сейчас весь тораффик швыряется через MSовские суперноды. И мы все под колпаком(надел шапочку из фольги)
          • +2
            Именем постановления правительства 8723, требуем снять шапочку или запустить Skype. Мы вас не слышим.
            • 0
              <irony>Вы всегда под колпаком, независимо через какие серваки трафик идёт</irony>
            • 0
              Skype изучают не для того что бы создавать открытых клиентов. А как минимум для того что бы изучить устройство и построение p2p сети. В которой skype был можно сказать первопроходцем. И уникальность его простой но в то же время я считаю интересной криптографии.
              • 0
                Сейчас чатики уже не p2p создаются. Причем кстати вёб-версия скайпа с p2p старыми чатами не работает. Только с облачными новыми. Вроде как.
                • 0
                  Что вы этим хотели сказать? И причем здесь чатики?
            • +5
              Не понимаю, в чём польза от этой статьи? Какие-то куски кода из вывода IDA, какие-то предположения, основанные на встретившихся в бинаре строках. Перед этим всем автор утверждает, что всё разобрал, но публиковать не будет.

              И чо?
              • +4
                Автор показал, что качественно разобрать протокол скайпа возможно, показал как работает транспортный уровень скайпа (пусть и не полностью). А главное, показал как надо проводить настоящий реверс инжиниринг больших проектов.
                • 0
                  Именно! И это с учетом того что я не все стал публиковать из за возможных последствий.
              • +1
                «Cвоя реализация bignum, rsa, обфускация rc4, транспортный уровень работы сетью, хеши, списки и т.д. Все очень по простому и компактно. Сейчас таких программ нет» — есть.
                netlab.linkpc.net/download/software/SDK/core/include
                компактная своя реализация ECDSA и гост 2001/2012, чача20, мд5 и сша — всё компактное и встраиваемое, никаких либ.
                • +1
                  Компактность != Удобство. У skype все на C++, а значит классы объекты… Другой подход, другие взгляды. Вот посмотрел ваш math_bn.h. Вроде прост, но везде тянутся указатели и в каждой функции проверка на валидность и return. Т.е. по моему мнению ваш код избыточен. Так же допустим у вас export/import le/be по две функции, у skype одна, а порядок le/be контролируется одним флагом на входе.

                  Можно так же отметить реализацию уникальность связных списков у skype на C++ template. К сожалению публиковать подробности не могу. Но такой реализации, я как программист и поныне не встречал даже на просторах google. А реализовал их skype в далеком 2004 году. Вообщем нельзя не восхищаться их работой.
                  • 0
                    Проверки на валидность компилятор сам выкидывает когда они не нужны.
                    То что оно возвращается при ошибке — крайне удобно в диагностике: дебаговая версия показывает всю цепочку вывзовов от места ошибки.

                    Сделать одну функцию не проблема, проблема в том, насколько быстро она будер работать, и насколько востребованна.
                    Я боролся за каждый if во время оптимизации.
                    Опять же, не везде нужен и импорт и экспорт и в ле и в бе одновременно.
                    • 0
                      У большинства программистов является проблемой сделать простые и компактные функции.
                      Проблемма не в колличестве if-оф. А в избыточном коде для программиста.
                      Изучите С++ и шаблоны. Это позволит вам упростить использование вашего bignum. Вот как пример
                      libcage/blob/master/src/bn.hpp
                      Похожий подход в шаблонах bignum в skype.

                      Я не говорил о том что нужна одновременная le+be. Но зачем нужно по две функции? Когда le/be это всего лишь направление чтения или записи. А значит всего лишь флаг дирекшина.
                      • 0
                        Цели у меня другие.
                        Я хочу чтобы мой код можно было легко засунуть хоть в ядро FreeBSD, хоть в ядро линукса, хоть в любую поделку на arm/mips и прочей мелкой херне, в любой проект.
                        Си есть везде и сопрягаются со всем. Плюсы нет.
                        Скайп — законченное приложение, у меня же криптопримитив.

                        Для меня лично си понятнее и прозрачнее, объём меня не сильно парит, до 2к строк в одном файле вполне легко перевариваю.

                        Две функции нужны для скорости, прежде всего.
                        Для простоты и возможности утаскивать код в другие места тоже.
                        Если бы была одна то мне пришлось бы либо добавлять условия внутрь цикла и это была бы просадка скорости, либо городить рядом два цикла.
                        Можно было вообще сделать монстра который и импорт и экспорт и be/le и bin/hex, всё в одном, но мне это не это не нравится.
                        Меня напрягает виндовый CreateFile() — где нужно каждый раз в SDK смотреть чтобы параметры правильно вписать, а open() скорее радует потому что всё просто и понятно.
                        Я уже писал в плюсах с классами, и делал функции «всё в одном» — пользоватся этим потом было не удобно: отчасти потому что я плохо понимал классы, от части потому что было слишком дохрена параметров в слишком универсальных функциях, что в конечном счёте приводило к тому что пользоватся результатом было не удобно даже в том проекте для которого оно писалось, не говоря уже об использовании в других проетах.

                        Конкретно этот код был использован в трёх проектах (даже четырёх, если считать то что я отлаживал его иногда вижал студии на пустом проекте). Все проблемы с переносом начинались и заканчивались добавлением инклюда в проект, а перед ним дефайнов с настройками. Ещё отладочный вывод пришлось отдельный написать для винды.
                        • 0
                          Скайп — законченное приложение, у меня же криптопримитив.

                          Скайп собран из таких же кпипто примитивов, только более обдуманно. И каждый из них можно вытянуть куда угодно, хоть в ядро.
                          С++ есть на любой arm/mips итд. Легко запихиваются в ядро linux/freebsd/windows.
                          С++ это умение правильно мыслить. При этом я не говорю о прекрасных знаниях математики. Можно увидеть много ужасных набросков кода/библиотек итд от людей которые очень хорошо знают математику.
                          Поэтому умение писать хороший код это совершенно другое.

                          Я не знаю как вы там пишите на С++. Но то что можно увидеть у вас на С — ужасное. При этом знание у математики и всяких алгоритмов у вас неплохое.
                    • 0
                      а в чем уникальность списков? это на столько элементарная структура, что там сложно что-то сделать уникального)
                      • 0
                        Он не использует new/malloc для выделения памяти под елемент списка.
                        И сама уникальность — он использует указатель на мембер ноды.
                        Знакомы в C++ с указателям на мембер функцию? skype в списках использует C++ указатель на мембер поля.
                        Я бы сказал очень редкое явление в C++. А для старых времен ~2003 годов и подавну.
                        • 0
                          у… батенька, вам сюда: www.boost.org/doc/libs/1_58_0/doc/html/intrusive.html

                          я-то думал, что там правда ми-ми-ми.
                          • 0
                            Я как программист знаю о boost. Год появления boost на сайте 2005-2006. Skype ~2003 год, а свои библиотеки и p2p они откатывали наверняка еще гараздо раньше.
                            • 0
                              Вы пишите, что «я как программист и поныне не встречал даже на просторах google». Я вам показал лишь, что поныне, а именно с 2008 года интрузивные контейнеры доступны в бусте и странно, что вы не нашли их на просторах google.

                              > Год появления boost на сайте 2005-2006

                              www.boost.org/users/history — ранее 1999.
                              • 0
                                В следующий раз буду стараться формулировать свои мысли более четко и ясно.
                                Подразумевалось что использование указатель на мембера поля, очень мало распространенный прием. Чаще используют указатель на мембер функцию в классе.
                                • 0
                                  Не согласен. Прием нестандартен для тех, кто использует С++ как С с объектами.

                                  Кроме того, у данной реализации списков есть одно не очень удобное свойство — необходимо заранее знать в каких и в скольких списках данный объект может находиться одновременно. Это такой caveeat, который многих отпукивает от их использования. А вовсе не потому, что реализовать такой список сложно.
                                  • 0
                                    Значит 99% кода из гугла и гитхаба это С с обьектами.

                                    Такие реализации тяжелы и в реверс инжиниринге. Скаляр который инициализируется в конструкторе и дальше по коду его не видно. При декомпиляции это заплывает. Хорошо если inline не развернулся и по логике можно прикинуть что это только вот в одной функции.
                  • 0
                    Хорошая статья, интересная и позновательная. Даже для людей далёких от темы.
                    Просто любопытно, что другим любопытно, как же это работает.
                    И самое главное что на это есть время, ведь это не за один вечер сделано, не так ли?
                    • 0
                      Обдумываю не написать ли продолжение-дополнение по этой статье.
                      Конечно не за один. Реверс инжиниринг skype производился по наличию свободного времени. Просчитать по времени сложновато. Были периоды на 2-3 дня без сна. И с перерывами на несколько месяцев и уход в другой реверс инжиниринг. Но если высчитывать в обычный рабочий день с 9-6 и 5 дневной рабочей недели то наверное
                      где то за 5 месяцев. Хотя именно в таком режиме это сделать не возможно было бы.
                      • 0
                        А можно вас спросить: вы для чего реверс делали? just for fun или была какая-то практическая польза?
                        • 0
                          Тратить на это время just for fun — нерационально.
                          • 0
                            Я думаю для практического применения инженерных задумок. Либо в целях проверки безопасности, может грант какой. А может быть и фирма Н решила перейти на безопасный протокол, и нужно убедиться что не сливается что-то налево :)
                          • 0
                            Хм, такой подход к работе меня обнадёживает. Я как бы тоже схватился за многое одновременно, но пока ещё ничего не закончил. Но вижу что обрывками можно тоже результата добиться, при том впечатляющего.
                            • 0
                              На картину из мозаики нужно смотреть издалека. При реверс инжиниринге очень часто утопаешь в деталях. Для этого и нужны паузы. Да я думаю и не только при реверсе, в других сферах тоже самое.

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