Ручная загрузка DLL

Discussion in 'WASM.BEGINNERS' started by SZ, Jan 5, 2011.

  1. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    Всем привет.
    Нашёл способ тут http://www.slesh.name/?act=articles&subact=show&nid=12
    Всё получилось и работает НО
    у меня возник вопрос, зачем автор грузит дллку по частям (хидеры, секции ...) ?
    Разве нельзя весь образ разом грузануть, а потом уже по нему пройтись и заполнить структуры ?
    вот кусок с вопросом
    Code (Text):
    1. ULONG                   retadr = 0;
    2. DWORD                   rb;
    3. HANDLE                  _hFile;
    4. IMAGE_DOS_HEADER        DosHeader;
    5. IMAGE_NT_HEADERS        PeHeader;
    6. IMAGE_SECTION_HEADER    Section[10];
    7. char                    tmp[1024];
    8.  
    9. _hFile = CreateFile(_filename,                  // открываемый файл
    10.                     GENERIC_READ|GENERIC_WRITE, // открываем для чтения/записи
    11.                     0,                          // совместно не используется
    12.                     NULL,                       // защита по умолчанию
    13.                     OPEN_EXISTING,              // только существующий файл
    14.                     FILE_ATTRIBUTE_NORMAL,      // обычный файл
    15.                     NULL);                      // атрибутов шаблона нет
    16. if(_hFile==INVALID_HANDLE_VALUE)_hFile=0;
    17.  
    18.     if(_hFile)
    19.     {
    20.     // Читаем DOS заголовок
    21.     ReadFile(_hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &rb, NULL);
    22.         if(DosHeader.e_magic == IMAGE_DOS_SIGNATURE)    // проверим DOS сигнатуру
    23.         {
    24.             // если есть какимето данные между DOS заголовком и PE
    25.             // то считаем их. В MS компиляторах это часто Rich данные
    26.             if(sizeof(IMAGE_DOS_HEADER) < DosHeader.e_lfanew)
    27.             {
    28.             ReadFile(_hFile, &tmp[0], DosHeader.e_lfanew - sizeof(IMAGE_DOS_HEADER), &rb, 0);
    29.             }
    30.         // установим указатель в файле на PE заголовок
    31.         SetFilePointer(_hFile, DosHeader.e_lfanew, NULL, FILE_BEGIN);
    32.         // Читаем PE заголовок
    33.         ReadFile(_hFile, &PeHeader, sizeof(IMAGE_NT_HEADERS), &rb, NULL);
    34.             if(PeHeader.Signature == IMAGE_NT_SIGNATURE)// проверим PE сигнатуру
    35.             {
    36.             // считаем 10 секций
    37.             ReadFile(   _hFile,
    38.                         &Section[0],
    39.                         sizeof(IMAGE_SECTION_HEADER) * PeHeader.FileHeader.NumberOfSections,
    40.                         &rb,
    41.                         NULL);
    42.             // выделим памяти столько, сколько указано в SIZE OF BASE
    43.             retadr = (ULONG)VirtualAlloc(   0,
    44.                                             PeHeader.OptionalHeader.SizeOfImage,
    45.                                             MEM_COMMIT|MEM_RESERVE|MEM_TOP_DOWN,
    46.                                             PAGE_EXECUTE_READWRITE);
    47.                 if(retadr)  // если память выделилась
    48.                 {
    49.                 // скопируем туда DOS заголовок
    50.                 memcpy((void *)retadr, &DosHeader, sizeof(IMAGE_DOS_HEADER));
    51.                 // скопируем туда PE заголовок
    52.                 memcpy((void *)(retadr + DosHeader.e_lfanew),
    53.                                 &PeHeader,
    54.                                 sizeof(IMAGE_NT_HEADERS));
    55.                 // скопируем туда таблицу секций
    56.                 memcpy((void *)(retadr + DosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS)),
    57.                                 &Section[0],
    58.                                 sizeof(IMAGE_SECTION_HEADER)*PeHeader.FileHeader.NumberOfSections);
    59.                 // если есть Rich данные то и их тоже скопируем
    60.                     if(sizeof(IMAGE_DOS_HEADER) < DosHeader.e_lfanew)
    61.                         memcpy((void*)( retadr + sizeof(IMAGE_DOS_HEADER)),
    62.                                         &tmp[0],
    63.                                         DosHeader.e_lfanew- sizeof(IMAGE_DOS_HEADER));
    64.                     // обработаем каждую секцию
    65.                     for(int i = 0; i < PeHeader.FileHeader.NumberOfSections; i++)
    66.                     {
    67.                     // установим указатель в файле на начало секции в файле
    68.                     SetFilePointer(_hFile, Section[i].PointerToRawData, NULL, FILE_BEGIN);
    69.                     // считаем всё секцию
    70.                     ReadFile(_hFile,(void*)(retadr + Section[i].VirtualAddress),
    71.                                             Section[i].SizeOfRawData,
    72.                                             &rb,
    73.                                             NULL);
    74.                     }
    75.                     // Обработаем релоки
    76.                     if(!ProgressReloc(retadr))
    77.                     {
    78.                         //  если ошибка
    79.                         //  освободим память
    80.                         VirtualFree((void*)retadr, 0, MEM_RELEASE);
    81.                         retadr = 0;
    82.                     }       // обработаем импорт
    83.                     else    if(!ProgressImport(retadr))
    84.                             {
    85.                                 //  если ошибка
    86.                                 //  освободим память
    87.                             VirtualFree((void*)retadr, 0, MEM_RELEASE);
    88.                             retadr = 0;
    89.                             }//*
    90.                             else
    91.                             {
    92.                                 __asm
    93.                                 {
    94.                                 mov eax, PeHeader.OptionalHeader.AddressOfEntryPoint
    95.                                 add eax, retadr         // EAX = ENTRY POINT
    96.                                 push 0                  // _stdcall-> Arg3,Arg2,Arg1; ret n;
    97.                                 push DLL_PROCESS_ATTACH // ставим флаг что подгрузили DLL
    98.                                 push retadr
    99.                                 call eax    // передадим управление на точку входа в DLL
    100.                                 }
    101.                             }
    102.                 }
    103.             }
    104.         }
    105.     CloseHandle(_hFile);
    106.     }
    107. return (HMODULE)retadr;
    Помоему он полностью повторяет исх. файл, т.е. образ в памяти ничем не будет отличаться от файла.
    или нет ?
     
  2. RDMess

    RDMess Member

    Blog Posts:
    0
    Joined:
    Dec 28, 2009
    Messages:
    51
    Может ошибаюсь, но думаю если грузить сразу весь файл антивирусу будет легче осмыслить факт загрузки DLL.
     
  3. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    каждая секция должна быть выравнена на размер страницы...

    нет никакой разницы... есть много факторов по которым можно определить, что этот блок памяти является dll...
     
  4. Clyde

    Clyde New Member

    Blog Posts:
    0
    Joined:
    Mar 29, 2009
    Messages:
    154
    // Обработаем релоки
    if(!ProgressReloc(retadr))
    // обработаем импорт
    else if(!ProgressImport(retadr))

    такчто не совсем :)
     
  5. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    RDMess
    Антивирус дело десятое.
    Главный вопрос можно ли обойтись без этих манипуляций с секциями.
    Но получить абсолютно рабочий образ.
     
  6. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    я думаю только в одном случае... когда выравнивание секций в файле совпадает с выравниванием виртуальной памяти...
     
  7. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    Clyde
    Даг вот!
    это уже финиш, т.е. все необходимые данные к этому моменту УЖЕ скопированы в память.
    Собссно вопрос то в том, можно ли копировать не по частям а полностью как есть.
    Дальше чисто работа с данными в памяти, меняем относительные адреса на абсолютные, НО образ то уже не трогается.
     
  8. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
  9. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    Rel
    Но в функции загрузки выравнивание не упоминается.
    Первый раз оно встречается в релоках
    Code (Text):
    1. ULONG ProgressReloc(ULONG filebase)
    2. {
    3. ULONG       PE;
    4. ULONG       IB;
    5. ULONG       cnt;
    6. ULONG       x;
    7. ULONG       Delta;
    8. PFIXREC     fixrec;
    9. USHORT      fixtype;
    10. USHORT      fixoffset;
    11.  
    12. PE = *(ULONG *)(filebase + 0x3C) + filebase;// получаем адрес PE заголовка
    13. IB = *(ULONG *)(PE + 0x34);                 // IMAGE BASE
    14. if(filebase == IB) return 1;                // Если совпадает с загруженным адресом, то фиксить не нужно ничего
    15. Delta = filebase - IB;                      // выцесляем дельта смещение.
    16. if(!*(ULONG *)(PE + 0xA0))return 1;         // если нет релоков то выходим
    17. fixrec = (PFIXREC)(*(ULONG *)(PE + 0xA0) + filebase); // получаем адрес таблицы релоков
    18.     while(fixrec->BlockSize)        // если таблица не пуста
    19.     {
    20.     cnt = (fixrec->BlockSize - 8) >> 1; // вычеслим кол-во элементов
    21.         for(x = 0; x < cnt; x++)
    22.         {
    23.         fixtype = (fixrec->TOR[x]) >> 12;       // типа фиксации
    24.         fixoffset = (fixrec->TOR[x]) % 4096;    // офсет внутри 4-х килобайтового блока
    25.         if(!fixtype) continue;        // если 0, то фиксация не нужна
    26.             if(fixtype == 3)          // если 3, то прибавить дельта смещение
    27.             {
    28.             *(ULONG*)(filebase + fixoffset + fixrec->PageRVA) = *(ULONG*)(filebase + fixoffset + fixrec->PageRVA) + Delta;
    29.             }
    30.             else return 0;  // все остальные случае вызовут ошибку (хотя их и не будет теоретически)
    31.         }
    32.     fixrec = (PFIXREC)((ULONG) fixrec + fixrec->BlockSize);// следующая таблица релоков
    33.     }
    34. return 1;
    35. }
     
  10. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    вас носом ткнуть?

    PointerToRawData - смещение секции от начала файла
    VirtualAddress - смещение секции от базового адреса загрузки в виртуальной памяти с учетом выравнивания
    PointerToRawData != VirtualAddress кроме наверное одной исключительной ситуации, когда:
    и вообще говоря код в этой статье далеко не лучший из тех, что можно найти на просторах интернета... и еще более вообще говоря, вам нужно подробнее прочесть про формат PE-файлов и работу виндового загрузщика...

    релоки - как раз одна из причин, по которой файл не будет работать без должного выравнивания... если вы грузите файл без выравнивания, то необходимо фиксить релоки должным образом...
     
  11. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    Терь всё ясно )
    Спасибо Rel
     
  12. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    вообще если подумать о загрузке без выравнивания - задача интересная... тут надо хорошо знать как генерирует код компилятор, что собирает dll... впринципе это можно довольно просто сделать, если запихнуть весь код и данные в одну секцию, все переходы сделать относительными (позишн индепендент код)... я к тому, что если мы грузим нашу длл или экзе-файл, которую сами собираем - это возможно... если мы грузим нормальные бинарники, то ни о какой стабильности этого подхода не может быть и речи... много подводных камней...
     
  13. qwe8013

    qwe8013 New Member

    Blog Posts:
    0
    Joined:
    May 28, 2009
    Messages:
    198
    SZ
    Потому что PointerToRawData и VirtualAddress у секций могут быть разными.
     
  14. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    qwe8013
    Это уже понятно.
    Может разовьете идею Rel'a, насчёт, объединить все секции в одну ?

    У меня тока с .reloc такой фокус не проходит
    #pragma comment(linker,"/MERGE:.rdata=.text")
    #pragma comment(linker,"/MERGE:.data=.text")
     
  15. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    тут нужен асм... на сях сложно такое сделать... релоки ты не запихнешь в одну секцию, их можно отключить... но тогда нужно, чтобы все переходы, привязки к данным и тд в коде были относительными... как сишный компилятор заставить это делать, я не знаю... нужен пи-код (позишн индепендент), его наверное только на асме можно сделать... рекомендую yasm, он простой, легко интегрируется в твою любимую визуал студию, линковать можно студийным линкером, можно миксить x86 и x64 код в одном obj-файле...
     
  16. qwe8013

    qwe8013 New Member

    Blog Posts:
    0
    Joined:
    May 28, 2009
    Messages:
    198
    SZ
    Вы конечно можете создать только одну секцию, но загрузить весь файл разом всё-равно не получится ,из-за выравнивания.

    PS Гораздо проще загрузить файл с выравниваниями чем писать пи-код.
     
  17. Rel

    Rel Well-Known Member

    Blog Posts:
    2
    Joined:
    Dec 11, 2008
    Messages:
    5,317
    согласен с этим...
     
  18. KIV

    KIV Member

    Blog Posts:
    0
    Joined:
    Jul 16, 2009
    Messages:
    231
    Это да, хотя при определённых условиях написать пи-код достаточно легко.
    Например, fasm при генерации 64-битного кода генерирует пи-код. Главное использовать lea rax, [address] вместо mov rax, address. Поскольку в таком случае вся адресация идёт относительно указателя команд.
    В 32-битном режиме, разумеется, такой фокус не пройдёт (EIP не доступен напрямую для чтения), поэтому придётся повозится.

    Только вот вообще зачем грузить файл без выравнивания? Только разве что из спортивного интереса. Других случаев, когда целый PE файл надо так грузить я не вижу.
     
  19. SZ

    SZ New Member

    Blog Posts:
    0
    Joined:
    Aug 19, 2010
    Messages:
    30
    1. Сократить кол-во вызовов ReadFile с (4 + кол-во секций) до 1.
    2. Сделать загрузчик комактнее==быстрее.
    Для себя выбрал промежуточный вариант, загружаю за раз в память потом по этой копии делаю образ.
    убыток: двойной размер памяти.
     
  20. qwe8013

    qwe8013 New Member

    Blog Posts:
    0
    Joined:
    May 28, 2009
    Messages:
    198
    SZ
    Ну и на сколько по вашему он будет быстрее?
    И какой смысл сокращать кол-во вызовов ReadFile?