Underground InformatioN Center [&articles] 
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]

Прямой доступ к макросам в документах MS WORD

...Я нарушил второе правило пользования
лифтом и вышел сквозь стену...

С. Лукьяненко. Неделя неудач.

Введение

Продолжаем разбалтывать "кошмарные тайны кровавой фирмы Microsoft". J

Иногда хочется посмотреть на исходный текст макросов, живущих внутри документов MS Word, особенно когда имеется подозрение на наличие в них какого-то злонамеренного кода - вируса или троянца. В данной статье будет рассмотрено несколько способов, как это можно сделать для документов MS Word версий 97, 2000 и XP, и как автоматизировать этот процесс.

1. "Правильный" способ

Вообще-то, Microsoft дает пользователю возможность посмотреть на исходный текст макроса, написанный на VBA:

  1. в меню "Сервис" выбрать пункт "Макрос";
  2. потом выбрать "Макросы";
  3. затем в появившемся списке выбрать желаемую строчку (например, "AutoOpen");
  4. нажать на кнопку "Изменить";
  5. и в окне редактора Visual Basic for Application узреть, наконец, вожделенный исходник.

Существуют ряд модификаций этого способа, связанных с использованием Организатора, с копированием макросов из документа в шаблон, с прямым вызовом редактора VBA по Alt-F11 и т.п. Но подобно дверям некрасовского "парадного подъезда", которые открывались далеко не для всех, этот способ не гарантирует успеха никому. Некоторые "привилегированные" макросы (например, большинство современных вирусов) таким образом увидеть невозможно.

Но ведь есть и другие подходы, которые Microsoft принципиально не желает документировать...

2. Где живут макросы

Мы уже знаем, что файл MS Word - это сложный объект, имеющий формат структурированного хранилища (structured storage), а вся полезная информация хранится в нем в виде потоков (streams).

В MS Word 6.0/7.0 макросы находились вместе с текстом документа в одном общем потоке с именем "Word Document".

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

Кроме того, внутри документа MS Word одновременно могут присутствовать стразу ТРИ формы представления одного и того же макроса.

Во-первых, это исходный текст макроса, так называемый s-code. Он не виден "на просвет" внутри файла, т.к. сжат алгоритмом LZNT1.

Во-вторых, это результ компиляции текстового исходника в двоичный образ - так называемый псевдокод (или p-code). Он может исполняться виртуальной машиной MS Word.

Наконец, это подготовленный к исполнению exe-code (или ex- code, или executable code). Он присутствует не всегда, но если присутствует, то исполняется виртуальной машиной MS Word именно он.

S-code и p-code живут в потоках:

  • "newmacros" (обычно, это "свежесозданные" пользователем макросы);
  • "thisdocument" (обычно, это "служебные" макросы и вирусы, размножающиеся при помощи методов Import/Export);
  • в потоках с именами макросных модулей (например, макросы вируса Macro.Word97.Emlitch, размножающегося при помощи OrganizerCopy, живут в потоке с именем sfc).

Характерной особенностью всех таких потоков является сигнатура 01 16h 01.

Exe-code живет в потоках с именами "__srp_0", "__srp_1" и т.п.

Полезная ссылка: Stefan Kurtzhals. Aktuelle Entwicklungen im Bereich der Makroviren

3. Как получить доступ к потоку внутри документа

Зная формат структурированного хранилища, можно самостоятельно написать программу сканирования файла. Но мы пойдем другим путем.

Библиотека OLE2.DLL содержит средства для работы со структурированными хранилищами. Функция stgIsStorageFile() возвращает для файла признак, является ли он структурированным хранилищем или нет. Функция stgOpenStorage() открывает файл хранилища и возвращает "интерфейс" IStorage. Это - объект класса, содержащего свойства и методы для работы со структурированными хранилищами:

  • IStorage::OpenStorage() - открывает подкаталог, возвращая интерфейс для доступа к подчиненным каталогам;
  • IStorage::Release() - закрывает подкаталог;
  • IStorage::EnumElements() - возвращает интерефейс перечислителя с методами Next(), Skip(), Reset() и т.п.;
  • IStorage::OpenStream() - открывает поток, возвращая интерефейс IStream с методами Read(), Write() и т.п.

Вот пример программы, сканирующей для указанного DOC-файла внутренние потоки.

//******************************************************************************
// Демонстрационная программа прямого доступа к макросам внутри DOC-файлов              
// Компилятор Borland C/C++ v5.02                                                       
// (c) Климентьев К. aka DrMAD, Самара 2003                                            
//******************************************************************************
#include "windows.h"
#include "ole2.h"
#include "iostream.h"

#define MAXBUF 0xFFFF
#define NAMELEN 256

char Buf[MAXBUF];  // Буфер под поток
char SBuf[MAXBUF]; // Буфер под распакованный текст

// Рекурсивный обход дерева потоков. (c) DrMad
walk(char *s, LPSTORAGE ls) 
{
 OLECHAR FileName[NAMELEN];	// Unicode-имя для structured storage
 char StreamName[NAMELEN];	// ASCII-имя потока
 LPENUMSTATSTG lpEnum=NULL;	// Интерфейс перечислителя
 LPSTORAGE pIStorage=NULL;	// Интерфейс структурированного хранилища
 LPSTORAGE pIStorage2=NULL;	// Интерфейс хранилища нижнего уровня
 LPSTREAM pIStream=NULL;	// Интерфейс потока
 STATSTG stat;		// Очередная запись в каталоге
 ULONG uCount;		// Счетчик перечисления
 ULONG streamlen;		// Реальная длина потока
 ULONG ch_pos;		// Позиция чунка внутри потока

 if (!ls) // Первый вызов
  {
   mbstowcs(FileName, s, 256);
   StgOpenStorage(FileName,NULL,STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&
                                                                 pIStorage);
   walk("", pIStorage);
  }
 else // Повторный рекурсивный вызов
 {
  ls->EnumElements(0,NULL,0,&lpEnum);
  if (lpEnum)
  while (lpEnum->Next(1,&stat,&uCount)==S_OK)
   {
    if (stat.type==STGTY_STORAGE) // Это хранилище
     {
      ls->OpenStorage(stat.pwcsName,NULL,STGM_READ|STGM_SHARE_EXCLUSIVE,
                                                   NULL,0,&pIStorage2);
      walk("", pIStorage2);
     }
    else // Это поток
     {
      ls->OpenStream(stat.pwcsName,NULL,STGM_READ|STGM_SHARE_EXCLUSIVE,0,
                                                             &pIStream);
      pIStream->Read(Buf, MAXBUF, &streamlen);
      wcstombs(StreamName, stat.pwcsName, 256);
      ch_pos = search_chunk(Buf, streamlen); // См. ниже!
      if (ch_pos) view_src(StreamName, Buf, ch_pos); // См. ниже!
     }
    };
   ls->Release();
  }
}

int main(int argc, char* argv[]) {
 if (argc>1) walk(argv[1],NULL);
}

Недостатки подхода: невысокое быстродействие и проблемы доступа к запароленным и "слегка испорченным" документам.

Полезная ссылка: Артем Каев. ActiveX по шагам

4. Как найти упакованный исходник внутри потока

Упакованные исходные тексты макросов хранятся в потоках с сигнатурой 01 16h 01. В начале потока расположен заголовок, после него лежит блок p-code, а конец потока занимает s-code макроса. Довольно часто (примерно в 80% случаев) в заголовке по смещению 0Dh располагается 2-байтовый адрес в потоке для s-code. Но в 20% случаев в этом поле заголовка находится значение -1 (FF FF), и это означает, что s-code придется искать по-другому.

Есть "правильный" метод поиска, но он довольно зануден, поэтому я предлагаю более простой подход. Он основан на том, что s-code лежит во второй половине потока и организован в виде нескольких фрагментов упакованных данных (так называемых "чунков" - chunks). Первый из них имеет в начале сигнатуру, которую можно найти по маске 01 yz Bx, где xyz представляет собой 12-битовую длину чунка минус 3.

Например, макрос вируса Macro.Word97.TNT имеет по смещению 15В9h три байта 01 63h B6h. Это означает начало упакованного чунка длиной 663h+3=666h байтов.

Вот пример процедуры, находящей внутри потока начало упакованного s-code:

// Поиск упакованного макроса в потоке. (с) DrMad
ULONG search_chunk( char *Buf, ULONG streamlen) 
{
 ULONG ch_pos; // Позиция чунка внутри потока

 if ((Buf[0]==1)&&(Buf[1]==0x16)&&(Buf[2]==1))
  {
   ch_pos = streamlen/2;
   while ((ch_pos<(streamlen-3)) && ((Buf[ch_pos]!=1)||
                           ((Buf[ch_pos+2]&0xF0)!=0xB0))) ch_pos++;
   if (ch_pos < (streamlen-3)) return ch_pos;
  }
  return 0;
}

Полезня ссылка: Costin Raiu. Bottom of the Class

5. Как распаковать текст

Как уже отмечалось, s-code упакован алгоритмом LZNT1. Это вариант классического алгоритма LZ77 (самого первого из алгоритмов, придуманных Лемпелом и Зивом), отличающийся от своих многочисленных собратьев лишь способом кодировки выходного потока. (Кстати, алгоритм, примененный фирмой Microsoft в утилитах COMPRESS/EXPAND и в библиотеках LZEXPAND/ LZ32, реализует похожий, но чуть-чуть другой вариант LZ77).

Разобравшись в алгоритме (кстати, это очень интересно и поучительно!), можно написать собственный распаковщик, но мы в данной статье воспользуемся недокументированной функцией RtlDecompressBuffer(), живущей в библиотеке NTDLL.DLL из-под Windows NT/2000/XP.

Вот пример процедуры, распаковывающей исходный текст макроса и выводящей его на экран:

typedef UINT (WINAPI* RTLD)(ULONG, PVOID, ULONG, PVOID, ULONG, PULONG);

// Распаковка и показ макроса. (с) DrMad
view_src( char *StreamName, char *Buf, ULONG ch_pos) 
{
 HMODULE h;                // Хэндл библиотеки NTDLL.DLL
 RTLD RtlDecompressBuffer; // Указатель на функцию
 ULONG ulen;               // Длина распакованного фрагмента
 int i;

 LoadLibrary("ntdll.dll");
 h = GetModuleHandle("ntdll.dll");
 RtlDecompressBuffer = (RTLD) GetProcAddress(h, "RtlDecompressBuffer");
 if (RtlDecompressBuffer)
  {
   RtlDecompressBuffer(0x2, SBuf, MAXBUF, &(Buf[ch_pos+1]), MAXBUF-ch_pos, &ulen);
   cout << "-------------------" << StreamName << "-------------------" << endl;
   i=0; while (SBuf[i]) cout << SBuf[i++];
  }
}

Недостаток метода: невозможность работы в Windows 95/98/ME.

Полезные ссылки:
1. Alexandru Ionescu. NT (and XP) Native API Compression
2. Undocumented functions of NTDLL

6. Некоторые замечания

Вообще-то, можно также получить исходный текст макроса, декомпилировав p-code или exe-code. Профессиональные вирусологи именно так и делают, а потом анализируют алгоритмы макровирусов по декомпилированному и "очищенному" исходнику, в котором все "разложено по полочкам", отсутствуют лишние пробелы, пустые строки, комментарии и "заковыристые" имена переменных.

Кстати, Весселин Бончев даже опубликовал несколько лет назад статью, в которой с веселым изумлением обратил внимание общественности, что в процессе подобного анализа вирусологи фактически СОЗДАЮТ НОВЫЕ ВЕРСИИ ВИРУСОВ, и поэтому вполне могут быть за это посажены за решетку (особенно с учетом законопослушного до идиотизма западного менталитета).

Ну а мы будем просто смотреть на "авторский" текст.

Наличие вирусов в исходнике можно заподозрить, встретив методы Export/Import и/или OrganizerCopy. Также имеет смысл встревожиться, обнаружив методы типа AddFromFile, InsertLines, ReplaceLines и т.п., которые позволяют формировать текст копии макроса из отдельных строк. В принципе, этот процесс очень несложно автоматизировать.

Кроме того, пора бить тревогу, если исходник выглядит "страшненько", например, как вот этот фрагмент знаменитого (среди вирусологов) полиморфного макровируса PolyMac (он же Chydow):

    ...
    Do Until F0cKAuGp3 > 48
    F0cKAuGp3 = "F0cKAuGp3" + 8 : Loop:
    yMpAoI8 = yMpAoI8 + "@$qєfZ@" + Chr$(16) + "@{fZ3_Fq__zZSl"
    dpXaGhl5hvB8 = 7 : uTB5Xjcrdxv6 = 9
    Nlqmq1xRKFCPt3 = 0 : zwdbIKu4BTCmSP1 = J5 + rpFYZ6
    Do Until Nlqmq1xRKFCPt3 > 38
    Nlqmq1xRKFCPt3 = Nlqmq1xRKFCPt3 + 5 : Loop
    Do While uTB5Xjcrdxv6 < 41
    uTB5Xjcrdxv6 = uTB5Xjcrdxv6 + 3
    ...

Автоматизировать процесс распознавания такого рода заразы можно, лишь эмулируя исполнение p-code или exe-code и декодируя таким образом "тушку" вируса. "Но это уже совсем другая история".

Полезная ссылка: Vesselin Bontchev. The "Pros" and "Cons" of WordBasic Virus Upconversion

Заключение

Еще раз обращаю внимание читателей, что данная статья - не справочник, но побуждение к собственным исследованиям.

Дабы избежать некоторой порции упреков, спешу сообщить, что для решения некоторых рассмотренных выше задач я знаю более быстрые и мощные методы и алгоритмы. Но в статье были рассмотрены простые "заменители" для этих алгоритмов и методов, которые, тем не менее, позволили бы в итоге все-таки решить задачу. Удачно ли у меня получилось - решать Вам.

Кстати, я не буду сильно возражать, если компетентныме читатели начнут критиковать меня конструктивно, т.е. сами поделятся интересными сведениями на эту тему.

Выражаю признательность участнику проекта "антивирус Stop!" А. Каримову, в беседах с которым родились (и, надеюсь, будут продолжать рождаться) все мои статьи, посвященные исследованиям формата документов MS Word.

Вот пока и все. Благодарю за внимание.

С глубочайшим почтением и искреннейшей преданностию есмь, милостивые государи, Ваш покорный слуга,

(с) Климентьев К.Е. aka DrMad (drmad (at) dr.com),
Самара 2003-04

Статья написана специально для UInC (http://www.uinc.ru).

Microsoft Word, Binary Format, Structured storage, RtlDecompressBuffer, Структурированное хранилище, Поток, Макрос, Макровирус

Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы не отвечаем ни за какие последствия, которые имели место как следствие использования этих материалов\программ. Вы используете все вышеперечисленное на свой страх и риск.

Любые материалы с этого сайта не могут быть скопированы без разрешения автора или администрации.


[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]
 Underground InformatioN Center [&articles] Choose style:
2000-2015 © uinC Team