Russian
Интернет находится в нормальном состоянии, крупных эпидемий и других серьезных инцидентов службой мониторинга «Лаборатории Касперского» не зафиксировано. Уровень опасности: 1

Интерпретации байткода для x86 процессоров: подводные камни

Георг Вишерски
Эксперт «Лаборатории Касперского»
опубликовано 29 июл 2010, 11:56  MSK
0
 

В процессе работы по созданию эффективной системы generic-детектирования шелл-кода и проверки результатов на сгенерированных случайным образом входных данных мне пришлось применять фаззинг (fuzzing) для тестирования различных дизассемблеров с открытым исходным кодом. В качестве дизассемблера для своего нынешнего проекта я выбрал библиотеку libdasm, ориентируясь на относительно давнюю историю ее развития и лицензию, относящую ее к категории общественного достояния. Однако очевидно, что написание качественного и полного дизассемблера для x86 процессоров – нетривиальная задача в силу сложности набора команд процессоров, основанных на архитектуре x86.

В прошлом для libdasm были характерны проблемы, связанные с некорректным дизассемблированием некоторых инструкций с плавающей точкой, но они были вызваны простой ошибкой, которая заключалась в сдвиге значений в таблицах опкодов на три единицы (были пропущены три ряда со значениями NULL), и поэтому исправить ошибку было относительно легко.

Но сегодня я наткнулся на проблему, которая, по-видимому, связана не с опкодами, а с ошибкой, приводящей к неверной расшифровке инструкций. При дизассемблировании инструкций, использующих префикс размера адреса, libdasm его не учитывает, что приводит к получению неверного значения длины инструкции, вследствие некорректного распознования размера операнда, использующего непосредственный адрес:

[~] Verifying shellcode candidate offset 8eb0f0
  008fe0f0[    67a02232e830] > mov al,[0x30e83222]
  008fe0f6[              61] > popa 
  008fe0f7[              f9] > stc 
  008fe0f8[          ff4038] > inc [eax+0x38]
  008fe0fb[            b269] > mov dl,0x69
  008fe0fd[              52] > push edx
  008fe0fe[              3f] > aas 
  008fe0ff[              5e] > pop esi
  008fe100[    1a3dc31168aa] > sbb bh,[0xaa6811c3]
  008fe106[              59] > pop ecx
  008fe107[              9c] > pushf 
  008fe108[................] <

Инструкция по адресу 008fe0f0 виртуализированной памяти расшифровывается неверно:

  • 67 это вышеупомянутый префикс размера адреса
  • a0 это код операции mov al, moffs8
  • 2232 это 16-разрядный адрес, который должен интерпретироваться как операнд
  • e830 не относится к данной инструкции

Исходя из принципа, что при обнаружении экзотической болезни следует проконсультироваться со вторым врачом, я решил попытать счастья еще с одним дизассемблером – библиотекой udis86:

$ udcli -noff -32 -s `python -c 'print 0x8eb0f0'` -c 10 shellcode/urandom.bin 
67a02232         a16 mov al, [0x3222]    
e83061f9ff       call 0xfffffffffff96139 
40               inc eax                 

Отлично – в этот раз инструкция mov дизассемблировалась правильно. И, поскольку последовательность байт e830 уже не интерпретируется как часть операнда, указывающего на память инструкции mov, она теперь верно дизассемблируется как инструкция call rel32. К сожалению, udis86 – дизассемблер, поддерживающий набор команд x86-64, и он расширяет операнд инструкции call, до 64 битов с сохранением знака, вновь приводя к ошибке при дизассемблировании.

Какие же инструкции в действительности «видит» и исполняет мой процессор? Поскольку мы в любом случае имеем дело с эмуляцией кода, мы можем просто установить точку останова (int 3) в начале блока и начать пошаговую отладку под управлением gdb (за вычетом кое-какого мусора):

Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb) disas $eip, $eip+5
=> 0x0804b0c1:  jmp    0x804b134
(gdb) si
(gdb) disas $eip, $eip+10
Dump of assembler code from 0x804b134 to 0x804b13e:
=> 0x0804b134:  addr16 mov 0x3222,%al
   0x0804b138:  call   0x7fe126d
   0x0804b13d:  inc    %eax
End of assembler dump.
(gdb) si
(gdb) si
(gdb) disas $eip, $eip+10
Dump of assembler code from 0x7fe126d to 0x7fe1277:
=> 0x07fe126d:  Cannot access memory at address 0x7fe126d

Таким образом, процессор действительно видит инструкцию call и пытается ее выполнить. В данном случае это могло бы привести к катастрофическим результатам, поскольку позволило бы коду, использующему уязвимость, дающую возможность повышения привилегий для выполнения произвольного кода (вероятнее всего, шелл-коду) преодолеть защиту, обеспечиваемую виртуализацией. Чтобы можно было корректно обработать файл в виртуализаторе необходимо в программе заменить все инструкции, изменяющие регистр EIP, такие как CALL. Однако если такая инструкция не встречается в дизассемблерном листинге, то мы её не сможем корректно обработать.

После установки патча для libdasm (которая, как оказалось, вообще игнорирует префиксы размера адреса при разборе операндов) получаем правильно дизассемблированный код:

[*] 543 shellcode candidate offsets
[~] Verifying shellcode candidate offset 8eb0f0
  008fe0f0[        67a02232] > mov al,[0x3222]
  008fe0f4[................] <
Emulating 008fe0f4: call 0x894229
Emulating CALL instruction from 8fe0f9.

Усвоенные сегодня уроки:

  • Включение в процесс тестирования ПО фаззинга (fuzzing) с использованием произвольных вводных данных – отличная мысль, позволяющая (как в данном случае) выявлять интересные уязвимости. В данном случае использовать уязвимость все равно было бы очень непросто, поскольку сегменты кода и данных указывали на разные базовые адреса, но опытный злоумышленник, возможно, смог бы успешно осуществить атаку.
  • Общедоступная версия libdasm неверно дизассемблирует все инструкции с префиксом размера адреса, порождая интересные версии атаки на некоторые проекты, использующие эту библиотеку. Ждите выпуска патча для libdasm!


1 комментариев

Fixxxer

29 июл 2010, 14:41
0
 

Ждите выпуска патча для libdasm!

Так вроде ж Вы его уже установили, судя по статье? Не скромничайте, патч - в студию! :)

Для добавления комментариев необходимо


Bookmark and Share
Закладки