Скачиваем архивы

P.S: Пишу диплом, поэтому на время разучился мыслить не по ГОСТ-у
Недавно пришлось решать задачу обновления программы. Алгоритм получился довольно банальный:
  1. Получить текущую версию
  2. Сравнить с актуальной версией
  3. В случае несовпадения, скачать и заменить оригинальные файлы
Для решения поставленной задачи, было принято решение, использовать в качестве файлового сервера веб-сервер и производить обмен данными по протоколу HTTP (см. RFC 2616).
В качестве веб-сервера я поставил Apache, а программу писал на Си c использованием Winsock.

Основной фишкой проекта является то, что програмуля умеет скачивать и сама распаковывать архивы формата 7-zip (см. алгоритм LZMA), что может пригодиться для хитрого трояна, либо для обновления модулей ботнет-а.

Начнем с азов сетевого программирования.
SOCKET tcp_client(char *host, unsigned short port) {  
    struct sockaddr_in dest_addr;  
    struct hostent *hst;  
    SOCKET s;  
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;  
  
  
    dest_addr.sin_family = AF_INET;  
    dest_addr.sin_port = htons(port);  
  
    if ((dest_addr.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {  
        if (hst = gethostbyname(host))  
            ((unsigned long *) & dest_addr.sin_addr)[0] = ((unsigned long **) hst->h_addr_list)[0][0];  
        else {  
            close(s);  
            return -1;  
        }  
    }  
  
  
    if (connect(s, (struct sockaddr *) & dest_addr, sizeof (dest_addr))) return -1;  
  
    return s;  
  
}  
Как видно из названия функции, она создает соединение с указанным хостом (это может быть, как IP, так и адрес сайта) возвращая нам дескриптор открытого сокета [на всякий случай проверьте, какие значения в случае неудачи возвращает socket и connect, так как я скопировал функцию из своего *nix-ового кода и кстати в winsock нет функции close, есть closesocket]. При желании функцию htons, можно заменить на макрос, который справедлив лишь для ОС Windows:
#define HTONS(a) (((0xFF&a)<<8) + ((0xFF00&a)>>8))
Как известно TCP – потоковый протокол, и хотя данные передаются в IP-пакетах, размер пакета напрямую не связан с количеством данных переданных TCP. Поэтому нельзя с уверенностью сказать что при вызове recv мы получим заданное количество байт.
Для получения данных заданной длинны я использую такую функцию:
#define _SEND 0
#define _RECV 1
int (__stdcall *tcp_func)(SOCKET s,char* buf,int len,int flags);

int tcp_rs(char type,SOCKET s, char *buf, int len, int flags) {
 int total = 0;
 int n;
 tcp_func=(int (__stdcall *)(SOCKET,char *,int,int)) (type==_SEND) ? &send:&recv;

 while(total < len) {
  n = tcp_func(s, buf+total, len-total, flags);

  if(n>0) { total += n; }
  else if(n == 0) { 
   closesocket(s);
   return 0;
  }
  else {
   n=WSAGetLastError();
   closesocket(s);
   return (!n+1);
  }
 }

 return total;
}
которая в случае успеха возвращает количество переданных байт равных len, 0 в случае, если соединение было разорвано либо закрыто и (минус) номер ошибки, в случае неудачи вызова функции send/recv.

Отправляем GET запрос и получаем данные.
Но перед этим необходимо упомянуть о функциях для работы с памятью memcpy и memmem, так как они нам очень пригодятся.
void * __cdecl my_memcpy (void * dst,const void * src,size_t count)
{
 void * ret = dst;

 while (count--) {
  *(char *)dst = *(char *)src;
  dst = (char *)dst + 1;
  src = (char *)src + 1;
 }

 return(ret);
}

void * __cdecl my_memmem(const void *buf, const void *pattern, size_t buflen, size_t len)
{
 size_t i, j;
 char *bf = (char *)buf, *pt = (char *)pattern;

 if (len > buflen)
  return (void *)NULL;

 for (i = 0; i <= (buflen - len); ++i)
 {
  for (j = 0; j < len; ++j)
  {
   if (pt[j] != bf[i + j])
    break;
  }
  if (j == len)
   return (bf + i);
 }
 return NULL;
}
А для замены стандартных malloc и free
void free(void *ptr);
Функция free() освобождает место в памяти, на которое указывает ptr
я использовал макросы для работы с кучей:
HANDLE hHeap=GetProcessHeap();
#define _alloc(len) HeapAlloc(hHeap,HEAP_ZERO_MEMORY,len)
#define _free(p) HeapFree(hHeap,0,p)
#define _realloc(p,len) HeapReAlloc(hHeap,HEAP_ZERO_MEMORY,p,len)
Я долго переделывал эту функцию и в итоге пришел к такому варианту. Что-бы каждый раз не мучатся с определением конца HTTP заголовка, длинной и началом данных я придумал структуру:
// ------------------------- это все в h файле
#pragma pack(push,1) // вырубаем выравнивание
typedef struct _HTTP_DATA {
 unsigned char * header;
 unsigned char * data;
 unsigned int header_len;
 unsigned int data_len;
} HTTP;
#pragma pack(pop)

#define _OUT 1 // отладочный вывод
#define HTTP_PORT 80
//----------------------------------------------------------

BOOL http_get(const char *host,const char *get,HTTP * http)
{
 SOCKET sock;
 int len;
 char buff[1024];
 char * http_data,*c,*s;
 sock=tcp_client(host,HTTP_PORT);
 if(sock==INVALID_SOCKET) return FALSE;
 len = wsprintf(buff, "GET %s HTTP/1.0\r\n\
Host: %s\r\n\
User-agent: Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6\r\n\
Accept: */*;q=0.9\r\n\
Connection: close\r\n\r\n",get,host,host); 

 http->data=NULL;
 http->header=NULL;
 http->data_len=0;
 http->header_len=0;

#ifdef _OUT
 printf("HTTP GET http:\/\/%s%s...\n",host,get);
#endif

 if(tcp_rs(_SEND, sock, buff, len, 0)>0) {

  // получаем заголовки или весь пакет
  len = recv(sock, buff, sizeof (buff), MSG_PEEK);
  // выделяем память под буффер
  http_data=http->header=_alloc(len);
  if(tcp_rs(_RECV, sock, http_data, len, 0)>0) { 
   // разбираем заголовки
   c=my_memmem(http_data,"Content-Length: ",len,16); c+=16; s=c;
   while(*c++!=0x0d); *--c=0x00; // 0x0d 0x0a
   http->data_len=atoi(s);
   http->data=my_memmem(http_data,"\r\n\r\n",len,4); http->data+=4;
   http->header_len=http->data-http->header;

#ifdef _OUT
   
   printf("   Content-Length: %d\n   received: %d - %d%%\n",http->data_len,len,(len*100/(http->data_len+http->header_len)));
#endif
   // если мы получили полностью пакет
   if(len==(http->data_len+http->header_len)) {
    closesocket(sock);
    return TRUE; }
   // если нет то увеличиваем память под пакетик
   http_data=http->header=_realloc(http_data,(http->data_len+http->header_len));
   http->data=http->header+http->header_len;
   // и дополучаем данные
   if(tcp_rs(_RECV, sock, &http_data[len], (http->data_len+http->header_len)-len, 0)>0){
    #ifdef _OUT
   printf("   received: %d - 100%%\n",http->data_len);
#endif
    closesocket(sock);
    return TRUE;}
  }
  _free(http_data);
 }
 return FALSE;
}

//----------------------------------------------------------
// ну и сразу пример использования этой функции

int main() {
 WSADATA ws;
 hHeap=GetProcessHeap();
 WSAStartup( MAKEWORD( 2, 2 ), &ws );

 HTTP file;
 if(http_get("ya.ru","/",&file))
 {
  // тут делаешь все что угодно с данными
  _free(file.header); // и освобождаешь выделенную память
 }
 WSACleanup();
 return 0;
}
Ну и наконец самое интересное

Распаковываем архив.
Разработчики архиватора 7-zip любезно предоставили LZMA SDK, в которой при желании не так уж и сложно разобраться. Немного посидев с исходником \lzmaXXX\C\Util\7z\7zMain.c родилась функция распаковки:
#include "\lzmaXXX\C\7z.h"
#include "\lzmaXXX\C\7zAlloc.h"
#include "\lzmaXXX\C\7zCrc.h"
#include "\lzmaXXX\C\7zFile.h"
#include "\lzmaXXX\C\7zVersion.h"

static ISzAlloc g_Alloc = { SzAlloc, SzFree };

int unpack(char *arch_file)
{
 CFileInStream archiveStream;
 CLookToRead lookStream;
 CSzArEx db;
 SRes res;
 ISzAlloc allocImp;
 ISzAlloc allocTempImp;
 UInt16 *temp = NULL;
 size_t tempSize = 0;

 allocImp.Alloc = SzAlloc;
 allocImp.Free = SzFree;

 allocTempImp.Alloc = SzAllocTemp;
 allocTempImp.Free = SzFreeTemp;

 if (InFile_Open(&archiveStream.file, arch_file))
 {
  //PrintError("can not open input file");
  return 1;
 }

 FileInStream_CreateVTable(&archiveStream);
 LookToRead_CreateVTable(&lookStream, False);

 lookStream.realStream = &archiveStream.s;
 LookToRead_Init(&lookStream);

 CrcGenerateTable();

 SzArEx_Init(&db);
 res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);
 if (res == SZ_OK)
 {
  //-----------
  UInt32 i;

  /*
  if you need cache, use these 3 variables.
  if you use external function, you can make these variable as static.
  */
  UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
  Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */
  size_t outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */

     CSzFile outFile;
   size_t processedSize;
  for (i = 0; i < db.db.NumFiles; i++)
  {
   size_t offset = 0;
   size_t outSizeProcessed = 0;
   const CSzFileItem *f = db.db.Files + i;
   size_t len;

   // получаем имя файлика
   len = SzArEx_GetFileNameUtf16(&db, i, NULL);

   if (len > tempSize)
   {
    SzFree(NULL, temp);
    tempSize = len;
    temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0]));
    if (temp == 0)
    {
     res = SZ_ERROR_MEM;
     break;
    }
   }

   SzArEx_GetFileNameUtf16(&db, i, temp); 
   // -- получили распаковываем его в памяти
   res = SzArEx_Extract(&db, &lookStream.s, i,
    &blockIndex, &outBuffer, &outBufferSize,
    &offset, &outSizeProcessed,
    &allocImp, &allocTempImp);
   if (res != SZ_OK) break;

   // терь записываем на дисочек


   if (OutFile_OpenW(&outFile, temp)) { 
    // can not open output file
    res = SZ_ERROR_FAIL;
    break;
   }
   processedSize = outSizeProcessed;
   if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed)
   {
    //can not write output file;
    res = SZ_ERROR_FAIL;
    break;
   }
   if (File_Close(&outFile))
   {
    //can not close output file
    res = SZ_ERROR_FAIL;
    break;
   }


  }
  IAlloc_Free(&allocImp, outBuffer);
  //------
 }
 SzArEx_Free(&db, &allocImp);
 SzFree(NULL, temp);

 File_Close(&archiveStream.file);
 if (res == SZ_OK)
 {
  //printf("\nEverything is Ok\n");
  return 0;
 }
 return 1;
}
естественно для успешной компиляции исходники из LZMA SDK просто необходимы. Функция распаковывает архив в директорию из корой работает программа, при этом она не учитывает каталогов, а распаковывает лишь файлы из архива. Мне этого было вполне достаточно для обновления (замены) файлов в текущем каталоге.

Заставить корректно работать программу без CRT мне не удалось, скорее всего это связано с вычислениями в алгоритме LZMA, но программа вышла ~36 Кбайт. С CRT ~ 82 Кбайта с включенным выводом.

P.S.S: TCP/IP - Connection people! Надеюсь, что вы не зря потратили время на прочтение моей белеберды :)
blog comments powered by Disqus
сюда туда