• XSS.stack #1 – первый литературный журнал от юзеров форума

Bitcoin P2P PROTOCOL

Chococream

Старожил форума
Легенда
Регистрация
31.10.2009
Сообщения
347
Реакции
42
Bitcoin P2P PROTOCOL

План:
1)Права, защищающие этот документ.

2)Пролог.

3)Техническое описание протокола.

4)DNS seed.

5)STUN: getting external IP address.

6)Эпилог.

Текст:
1)
Во-первых данный документ носит лишь исследовательский характер и не является призывом к совершению
неправомерных поступков, нарушающих законодательство вашей страны!
Во-вторых незаконное копирование частей текста и любых других его составляющих карается законом
по защите авторских прав собственности!
Разрешено копирование на ваш ресурс, только с указанием автора текста, а также гиперссылки на
оригинальную тему.


2)Пролог
Вообщем-то различные методы осуществления коммуникации между ботами в сети прежде были описаны мною
в этой статье: https://xss.pro/index.php?topic=23932 (блог https://dlab.im/blog/23.html )
Из всех предложенных способов, я решил выбрать в качестве тестов 11 метод "Bitcoin".

В 2х словах: bitcoin - это P2P ЭПС(электронная платёжная система) написанная на C++ с Qt Framework.
Хорошей документации, в целом, по исходному коду и протоколу нет. Основные изменения в протоколе
публикуются в основном как BIP(bitcoin improvement proposals) на github/sourceforge.
Начиная с 08 года(момент публикации автором bitcoin) альфа-версии 0.1, происходит постепенная
"рассортировка" кода(блоки кочуют из одного файла в другой), вследствие чего, весь код наслаивается
будто лапша. Впрочем, это только моё мнение :)


Транзакции в сети проходят с участием пользователей, которые считают DOUBLE-SHA256 хэш от блока, набитого
присланными данными. Эти действия(выполнение proof-of-work) позволяют поддерживать стойкость сети
к различного рода атакам(примером может служить: double-spending attack).

Подробно об устройстве вы можете почитать в официальном доке: bitcoin.org/bitcoin.pdf


740px-Total_bitcoins_over_time.png

Будучи хорошо распиаренной системой, текущая стоимость всех добытых денег в системе почти 10
миллиардов долларов(статистика - coinmarketcap.com).


3)Техническое описание протокола
Bitcoin использует TCP для передачи данных между нодами.
Существуют всего 2 сети: основная(порт 8333) и тестовая(порт 18333). Сеть поддерживает IPv4, IPv6 адреса.

Структура пакета:
msg_head.png


Я выделил 14 основных команд: version, verack, getaddr, addr, inv, getdata, getblocks, getheaders, tx, block, headers, alert, ping, submitorder, checkorder, reply.

version - информация о версии bitcoin и количестве блоков. Передаётся при первом подключении.
verack - передаётся в ответ на пакет "version".
getaddr - запрос на список доступных нодов.
addr - список IP адресов с портами.
inv - передаётся, когда нод получает новый блок с транзакциями.
getdata - запросить блок или транзакцию по хэшу.
getblocks - запросить блоки в виде пакета "inv" в определённом промежутке.
getheaders - запросить блоки в виде пакета "headers" в определённом промежутке.
tx - отправить транзакцию. Этот пакет передаётся в ответ "getdata".
block - отправить блок. Этот пакет передаётся в ответ "getdata".
headers - отправить 2000 заголовков блоков.
alert - отправить предупреждающее сообщение(сообщение верифицируется и только ведущий разработчик,
т.е хозяин закрытого ключа может его отправить).
ping/pong - просто проверка того, что соединение до сих пор актуально.
submitorder, checkorder, reply - используется при транзакциях на IP адрес.

Существуют 2 способа(документированных) найти пиры:
(I) DNS seeds(парсинг IP из ответа TYPE_A DNS серверов).
(II) Вшитый в bitcoin клиент список IP пиров.

При подключении, мы посылаем пакет "version", который содержит в себе: версию клиента, номер блока и
локальное время. Нод верифицирует пакет и в случае успеха шлёт нам "verack", а затем свой пакет "version".

Затем, чтобы обменяться списками нодов или же получить актуальный список, мы шлём пакет "getaddr".
Нод отвечает пакетом "addr", содержащим список подключённых к нему других нодов.

До 0.6 версии был ещё способ получения пиров через IRC сеть, но его ликвидировали.
Я описал необходимый минимум для создания базового проекта, приступим к рассмотрению его частей.


4)DNS seed
Я упомянул выше, что в процессе поиска пиров используется парсинг IP из ответа TYPE_A DNS серверов.

DNS сервера для основной сети:
WCHAR *MainNetDNSSeed[] = { L"seed.bitcoin.sipa.be", L"dnsseed.bluematt.me",
                            L"dnsseed.bitcoin.dashjr.org", L"bitseed.xf2.org" };

DNS сервера для тестовой сети:
WCHAR *TestNetDNSSeed[] = { L"testnet-seed.bitcoin.petertodd.org", L"testnet-seed.bluematt.me" };

Исходник:
#define DNS_BUFFER_SIZE 0x1000

HANDLE dns_request(WCHAR *pHost) {
    HANDLE hEvent, pDnsBuf, pPeerList;
    SOCKET pSocket;
    DWORD DnsBufSize, RecvSize;
    sockaddr_in sAddr, rAddr;
    PDNS_RECORD pDnsRecord;

    pDnsBuf = AllocMem(DNS_BUFFER_SIZE);
    if(!pDnsBuf) {
        return FALSE;
    }

    pPeerList = AllocMem(4096);
    if(!pPeerList) {
        return FALSE;
    }

    DnsBufSize = DNS_BUFFER_SIZE;

    if(!DnsWriteQuestionToBuffer_W((PDNS_MESSAGE_BUFFER)pDnsBuf,&DnsBufSize,pHost,DNS_TYPE_A,\
                               GetTickCount(),TRUE)) {
        goto exit_2;
    }

    sAddr.sin_family      = AF_INET;
    sAddr.sin_port        = htons(53);
    sAddr.sin_addr.s_addr = 0x04040808;   // google public dns server

    pSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(pSocket == INVALID_SOCKET) {
        goto exit_2;
    }

    hEvent = WSACreateEvent();
    if(WSAEventSelect(pSocket,hEvent,FD_READ)) {
        goto exit_1;
    }

    if( ( sendto(pSocket,(char *)pDnsBuf,DnsBufSize,NULL,(sockaddr *)&sAddr,\
          sizeof(sAddr)) ) != DnsBufSize ) {
        goto exit_1;
    }

    if(WaitForSingleObject(hEvent,5*1000)) {
        goto exit_1;
    }

    DnsBufSize = sizeof(rAddr);
    RecvSize   = recvfrom(pSocket,(char *)pDnsBuf,DNS_BUFFER_SIZE,NULL,\
                          (sockaddr *)&rAddr,(int *)&DnsBufSize);
    if(!RecvSize || RecvSize == SOCKET_ERROR) {
        goto exit_1;
    }

    DNS_BYTE_FLIP_HEADER_COUNTS((PDNS_HEADER)pDnsBuf);

    if( DnsExtractRecordsFromMessage_W((PDNS_MESSAGE_BUFFER)pDnsBuf,RecvSize,\
                                       &pDnsRecord) ) {
        goto exit_1;
    }

    RecvSize = (DWORD)pPeerList;
    while(pDnsRecord) {

        if( pDnsRecord->wType != DNS_TYPE_A) {
            pDnsRecord = pDnsRecord->pNext;
            continue;
        }

        *(int *)pPeerList = pDnsRecord->Data.A.IpAddress;
        pPeerList = (int *)pPeerList + 1;
        pDnsRecord = pDnsRecord->pNext;

    }

    DnsRecordListFree((PDNS_RECORD)pDnsRecord,DnsFreeRecordList);

exit_1:
    CloseHandle(hEvent);
    closesocket(pSocket);

exit_2:
    FreeMem(pDnsBuf);

return (HANDLE)RecvSize;
}

Приведённая функция возвращает handle на выделенную память с адресами нодов.
В полном исходнике, который я приложу в конце статьи нет функции для создания своего пира.
В принципе ничего сложного нет - достаточно открыть порт(8333 - если основная сеть, 18333 - если
тестовая сеть) и принимать входящие соединения.
Процесс авторизации аналогичен процессу подключения (пункт 3, техническое описание протокола),
за исключением того, что:

(I) Вам придётся принимать входящие команды. Из основных я бы выделил только частоприходящие - это
"inv", "getaddr", "addr". На пакеты "inv" и "addr" в принципе можно не отвечать.
На пакет "getaddr" потребуется ответить пакетом "addr" с указанием своего IP, а также других.

(II) Время от времени надо слать ping/pong для поддержания соединения, а также свой IP в пакете "addr".

Для тех, кто заинтересован в протоколе, прикладываю также в архиве с сорцами документацию.


5)STUN: getting external IP address
В самом bitcoin для получения внешнего IP, парсится два сайта: checkip.dyndns.org и showmyip.com/simple/
Я решил пойти другим более стабильным путём, а именно через STUN(Session Traversal Utilities for NAT).

Более подробно о STUN читайте тут: tools.ietf.org/html/rfc3489

В проекте, функция лишь используется для проверки того, находится ли нода за NAT.
Акцентировать много внимания на нём я не буду, приведу сразу исходник:
#define STUN_DEFAULT_PORT 3478
#define MAPPED_ADDRESS    0x0001
#define CHANGE_REQUEST    0x0003
#define Binding_Request   0x0001
#define Binding_Response  0x0101

char *StunServers[][2] = { "stun.stunprotocol.org", 0,
                           "stun.ekiga.net",      0,
                           "stun.ideasip.com",    0,
                           "stun.iptel.org",      0,
                           "stun.rixtelecom.se",  0,
                           "stun.schlund.de",     0 };

#define bswap16(x) ((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))

#define bswap32(x) ((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) \
        | (((x) >> 8) & 0xff00) | (((x) >> 24) & 0xff))


#pragma pack(push, 1)
typedef struct _STUN_MSG {
    WORD  msgType;
    WORD  msgLength;
    BYTE  id[16];
    WORD  type;
    WORD  length;
    DWORD value;

} STUN_MSG;
#pragma pack(pop)

#define STUN_BUFFER_SIZE 0x1000

int GetExternalIP() {
    SOCKET      pSocket;
    sockaddr_in sAddr, rAddr;
    hostent     *hostent_s;
    int         ExternalIP, len, RecvSize;
    HANDLE      hEvent;
    STUN_MSG    *pBuf;
    STUN_MSG    stun;

    pBuf = (STUN_MSG *)AllocMem(STUN_BUFFER_SIZE);
    if(!pBuf) {
        return FALSE;
    }

    pSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(pSocket == INVALID_SOCKET) {
        return FALSE;
    }

    hEvent = WSACreateEvent();
    if(WSAEventSelect(pSocket,hEvent,FD_READ)) {
        goto exit_2;
    }

{
    for(int i = 0; i < sizeof(StunServers)/sizeof(StunServers[0]); i++) {

        sAddr.sin_family = AF_INET;

        if(!StunServers[ i ][1]) {
            sAddr.sin_port = htons(STUN_DEFAULT_PORT);
        } else {
            sAddr.sin_port = htons((WORD)StunServers[ i ][1]);
        }

        hostent_s = gethostbyname(StunServers[ i ][0]);
        if(!hostent_s) {
            continue;
        }

        sAddr.sin_addr.s_addr = *(int *)hostent_s->h_addr_list[0];

        RtlZeroMemory(&stun, sizeof(stun));
        stun.msgType   = bswap16(Binding_Request);
        stun.msgLength = bswap16(0x8);                    // sizeof(type + length + value)
        RAND_bytes((DWORD *)&stun.id, sizeof(stun.id));   // Transaction ID
        stun.type   = bswap16(CHANGE_REQUEST);
        stun.length = bswap16(0x4);

back:
        len = sizeof(stun);
        if( sendto(pSocket,(char *)&stun,len,NULL,(sockaddr *)&sAddr,\
                   sizeof(sAddr)) != len ) {
            continue;
        }

        if(WaitForSingleObject(hEvent,5*1000)) {
            goto back;
        }

        RtlZeroMemory(pBuf, STUN_BUFFER_SIZE);
        len = sizeof(rAddr);
        RecvSize = recvfrom(pSocket,(char *)pBuf,STUN_BUFFER_SIZE,NULL,\
                            (sockaddr *)&rAddr,(int *)&len);
        if(!RecvSize || RecvSize == SOCKET_ERROR) {
            continue;
        }

        if(bswap16(pBuf->msgType) == Binding_Response) {

            for(int i = 0; i < bswap16(pBuf->msgLength); ) {

                if( bswap16((pBuf->type + i)) == MAPPED_ADDRESS) {
                    ExternalIP = *(int *)( ((int)&pBuf->value + i) + 4 );   // Family=0x01, Port = 0x****
                    break;
                }
                i += bswap16(pBuf->length) + 4;   // sizeof(type) + sizeof(length)
            }

            break;
        }

    }

}
exit_2:
    CloseHandle(hEvent);
    closesocket(pSocket);
    FreeMem(pBuf);

return ExternalIP;
}


6)Эпилог
Моя статья - это лишь демонстрация PoC(Proof of concept). В мои планы не входило создание самодельного
протокола для общения между ботами поверх основной сети и верификации команд через ассиметричный
криптоалгоритм. В исходниках я приложил базовые основы для создания своего пира.
В качестве примера, программа подключается к тестовой или основной сети и собирает nodes list.
При желании, вы сможете сотворить из этого нечто большее :) Будут вопросы - обращайтесь.


Архив с сорцами:
LINK: https://www.dropbox.com/s/80xcvndix9guns8/abc.rar?dl=1
PASS: damagelab_newyear2014_UG()!*&@W()!*G)(!@*G)_!@YG)!*)JWQ

Respects goes 2 all members of xss.pro/, которые так или иначе поддерживают меня.
Спасибо вам ребята за моральную поддержку! Поздравляю всех с наступающим 2014 годом!
Напоследок посоветую всем перечитать первый пункт :)

Постскриптум:
Честно, удивительно, что до этого никто не создал сеть подобную bitcoin. Идея, грубо говоря витала
в воздухе и любой подкованный программист выше среднего уровня с хорошим знанием криптографии, смог бы
создать такого рода систему. Может просто не было ТЗ ? :)


Критика сорцов - не принимается :). Писал код ровно 3 дня напролёт.
 
pic_giant_062013_SM_Bitcoin.jpg
Всем хорошего настроения. Итак, у нас новый обзор. На этот раз будем рассматривать Bitcoin P2P PROTOCOL глазами нашего модератора Chococream. (лайки ставить ему)


p.s. обзор смотрите выше :)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх