Архитектура 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-протокола, которое, я считаю, дает больше информации в понимании как устроена сетевая часть.