Исследуем китайские роутеры на RT5350
Однажды, lolipop купил роутер на алиэкспрессе. Да не простой роутер, а очень компактный и дешевый, с 2 Ethernet-портами, USB, да еще и от фирмы, которая в начале 2000-х продавала свои mp3-плееры на территории РФ: Nexx WT1520H.Стандартная прошивка, как и почти всегда, была скудная, и, конечно же, хотелось заменить ее на что-то более вменяемое. Но вот незадача — никаких альтернативных прошивок под роутер нет, и прошить непонятно как, т.к. никакие другие прошивки не принимались через веб-интерфейс, заголовок прошивки я раньше такой не видел, да и binwalk ничего в ней не находил, стало быть, она зашифрована: 00000000 32 33 35 30 6b d9 39 00 00 00 0e 02 00 00 00 00×2350k.9…| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 3e 19 53 c5 |…>.S.| 00000020 63 f5 51 9f 82 74 2d 03 2e 2f 1f 32 9c 4a 93 96 |c.Q…t-…/.2.J…| 00000030 15 82 23 d0 b2 7e d7 1b 13 c3 1b 1f 06 fa f8 e0 |…#…~…| 00000040 bb 43 9b c6 ee fc 4b 7a e6 50 71 2b f4 f3 95 c3 |.C…Kz.Pq+…| 00000050 63 d0 a3 9c 92 2e 16 c6 19 1c 4a 93 cb 95 c3 63 |c…J…c| 00000060 d2 9b 1a f5 2e 16 c6 19 1c 4a 93 f9 68 3c 9c 73 |…J…h<.s| 00000070 14 63 d5 10 5e d3 6b 25 2b c2 2e 07 eb 85 73 25 |.c..^.k%+.....s%| 00000080 9b 6b c0 f2 d8 9b cf 65 56 ac a9 c2 28 61 dd 55 |.k.....eV...(a.U| 00000090 18 a4 5b e9 ba 11 93 ec 30 76 4f 40 c1 f0 7c cb |..[.....0vO@..|.| 000000a0 36 d3 b3 93 fe 3d 6b 10 66 fa 43 39 f2 f6 c0 91 |6....=k.f.C9....| lolipop слил данные с флешки через программатор, и мы начали в них ковыряться.Исследование прошивки За весь процесс инициализации оборудования и запуска программ отвечает собственный инит — /sbin/rc. Это такой комбаин покруче busybox, в котором содержится практически вся логика работы роутера:Скрытый текст .text:0040E758 .globl start_services .text:0040E758 start_services: # CODE XREF: main+14C8p .text:0040E758 # DATA XREF: main+14C0o ... .text:0040E758 .text:0040E758 var_10 = -0x10 .text:0040E758 var_8 = -8 .text:0040E758 .text:0040E758 li $gp, 0x7EBF8 .text:0040E760 addu $gp, $t9 .text:0040E764 addiu $sp, -0x20 .text:0040E768 sw $ra, 0x20+var_8($sp) .text:0040E76C sw $gp, 0x20+var_10($sp) .text:0040E770 la $t9, start_syslog .text:0040E774 nop .text:0040E778 jalr $t9 ; start_syslog .text:0040E77C nop .text:0040E780 lw $gp, 0x20+var_10($sp) .text:0040E784 nop .text:0040E788 la $t9, start_proftpd .text:0040E78C nop .text:0040E790 jalr $t9 ; start_proftpd .text:0040E794 nop .text:0040E798 lw $gp, 0x20+var_10($sp) .text:0040E79C nop .text:0040E7A0 la $t9, start_telnetd .text:0040E7A4 nop .text:0040E7A8 jalr $t9 ; start_telnetd .text:0040E7AC nop .text:0040E7B0 lw $gp, 0x20+var_10($sp) .text:0040E7B4 nop .text:0040E7B8 la $t9, load_smb_driver .text:0040E7BC nop .text:0040E7C0 jalr $t9 ; load_smb_driver .text:0040E7C4 nop .text:0040E7C8 lw $gp, 0x20+var_10($sp) .text:0040E7CC nop .text:0040E7D0 la $t9, sys_led_init .text:0040E7D4 nop .text:0040E7D8 jalr $t9 ; sys_led_init .text:0040E7DC li $a0, 2 .text:0040E7E0 lw $gp, 0x20+var_10($sp) .text:0040E7E4 nop .text:0040E7E8 la $t9, start_upnp .text:0040E7EC nop .text:0040E7F0 jalr $t9 ; start_upnp .text:0040E7F4 nop .text:0040E7F8 lw $gp, 0x20+var_10($sp) .text:0040E7FC nop .text:0040E800 la $t9, start_dhcpd .text:0040E804 nop .text:0040E808 jalr $t9 ; start_dhcpd .text:0040E80C nop .text:0040E810 lw $gp, 0x20+var_10($sp) .text:0040E814 nop .text:0040E818 la $t9, start_ntpc .text:0040E81C nop .text:0040E820 jalr $t9 ; start_ntpc .text:0040E824 nop .text:0040E828 lw $gp, 0x20+var_10($sp) .text:0040E82C nop .text:0040E830 la $t9, start_dns .text:0040E834 nop .text:0040E838 jalr $t9 ; start_dns .text:0040E83C nop .text:0040E840 lw $gp, 0x20+var_10($sp) .text:0040E844 nop .text:0040E848 la $t9, start_ddns .text:0040E84C nop .text:0040E850 jalr $t9 ; start_ddns .text:0040E854 nop .text:0040E858 lw $gp, 0x20+var_10($sp) .text:0040E85C nop .text:0040E860 la $t9, start_igmp_proxy .text:0040E864 nop .text:0040E868 jalr $t9 ; start_igmp_proxy .text:0040E86C nop .text:0040E870 lw $gp, 0x20+var_10($sp) .text:0040E874 nop .text:0040E878 la $t9, start_ipmac_bind .text:0040E87C nop .text:0040E880 jalr $t9 ; start_ipmac_bind .text:0040E884 nop .text:0040E888 lw $gp, 0x20+var_10($sp) .text:0040E88C nop .text:0040E890 la $t9, start_block_ipmac .text:0040E894 nop .text:0040E898 jalr $t9 ; start_block_ipmac .text:0040E89C nop .text:0040E8A0 lw $gp, 0x20+var_10($sp) .text:0040E8A4 nop .text:0040E8A8 la $t9, start_block_port .text:0040E8AC nop .text:0040E8B0 jalr $t9 ; start_block_port .text:0040E8B4 nop .text:0040E8B8 lw $gp, 0x20+var_10($sp) .text:0040E8BC nop .text:0040E8C0 la $t9, start_ddos .text:0040E8C4 nop .text:0040E8C8 jalr $t9 ; start_ddos .text:0040E8CC nop .text:0040E8D0 lw $gp, 0x20+var_10($sp) .text:0040E8D4 nop .text:0040E8D8 la $t9, start_monitor_rate .text:0040E8DC nop .text:0040E8E0 jalr $t9 ; start_monitor_rate .text:0040E8E4 nop .text:0040E8E8 lw $gp, 0x20+var_10($sp) .text:0040E8EC nop .text:0040E8F0 la $t9, start_upgraded .text:0040E8F4 nop .text:0040E8F8 jalr $t9 ; start_upgraded .text:0040E8FC nop .text:0040E900 lw $gp, 0x20+var_10($sp) .text:0040E904 nop .text:0040E908 la $t9, start_conntrack_limit .text:0040E90C nop .text:0040E910 jalr $t9 ; start_conntrack_limit .text:0040E914 nop .text:0040E918 lw $gp, 0x20+var_10($sp) .text:0040E91C nop .text:0040E920 la $t9, start_macfilter .text:0040E924 nop .text:0040E928 jalr $t9 ; start_macfilter .text:0040E92C nop .text:0040E930 lw $gp, 0x20+var_10($sp) .text:0040E934 nop .text:0040E938 la $t9, start_black_management .text:0040E93C nop .text:0040E940 jalr $t9 ; start_black_management .text:0040E944 nop .text:0040E948 lw $gp, 0x20+var_10($sp) .text:0040E94C nop .text:0040E950 la $t9, start_wlan_wps .text:0040E954 nop .text:0040E958 jalr $t9 ; start_wlan_wps .text:0040E95C nop .text:0040E960 lw $gp, 0x20+var_10($sp) .text:0040E964 nop .text:0040E968 la $t9, start_trakerurl .text:0040E96C nop .text:0040E970 jalr $t9 ; start_trakerurl .text:0040E974 nop .text:0040E978 lw $gp, 0x20+var_10($sp) .text:0040E97C lw $ra, 0x20+var_8($sp) .text:0040E980 move $v0, $zero .text:0040E984 jr $ra .text:0040E988 addiu $sp, 0x20 .text:0040E988 # End of function start_services Путем дедукции и nmap было выяснено, что на роутере запущен telnetd, который доступен через WAN-интерфейс! Вот это дела! Однако, залогиниться не получалось ни под пользователем root, ни под пользователем admin.В качестве telnetd выступает busybox. Давайте заглянем в него (функция login_main):
Т-а-а-к, теперь пускает до ввода пароля, однако стандартный пароль «admin» не принимается. Интересно. Смотрим дальше:
Вот так дела! Логин nexxadmin, пароль y1n2inc.com0755, с доступом через WAN.В прошивке есть mtd_write, так что ничто не мешает нам уже сейчас залить OpenWRT прямо на флеш, что и было сделано lolipop, но все же было интересно разреверсить алгоритм шифрования. К сожалению, моих навыков ассемблирования MIPS в голове явно недостаточно, и я испытывал большой дискомфорт только смотря на весь этот код, поэтому я заказал себе такой же роутер, и, о чудо, через 2 месяца он был у меня.
Продолжаем исследование Подключен только RX, земля общая с лаптопом по USB-питанию.Обновить роутер можно как через веб-интерфейс, так и по tftp (который, опять же, слушает WAN!). Tftp-демон (upgraded из rc), похоже, сломан, т.к. обновление через него не приводило к обновлению прошивки в роутере, хотя и никаких ошибок не было. Следует заметить, что обновление прошивки по tftp требует аутентификации с таким же паролем, как и на веб-интерфейс, так что тяжело назвать его backdoor, скорее просто неправильно сконфигурированный сервис.
Я решил исследовать обновление прошивки именно через tftp.Простое обновление через стандартный tftp-клиент приводило к «Upgrade not possible: Incorrect Password» от upgraded. Давайте заглянем в него:
Обычные клиенты, похоже, не умеют отправлять tftp-опции, поэтому я скачал python-библиотеку tftpy, модифицировал одну строку в файле-примере клиента, и все заработало:
--- tftpy_client.py 2014–09–30 21:48:57.375550027 +0400 +++ tftpy_client.py_ 2014–09–30 21:48:50.355520342 +0400 @@ -83,7 +83,7 @@ progresshook = Progress (tftpy.log.info).progresshook — tftp_options = {} + tftp_options = {'admin': ''} if options.blksize: tftp_options['blksize'] = int (options.blksize) if options.tsize: Отлично! Теперь осталось разобраться с шифрованием прошивки. В rc есть функция decrypto, выглядит она как-то так:
Найти ключ в статике я не смог, поэтому я приступил к отладке. Чтобы отлаживать что-то на устройстве, разумеется, нужно сначала собрать отладчик. linux_server от IDA Pro не собирают под MIPS (а роутер построен именно на этой архитектуре), так что нужно было как-то собрать gdbserver под роутер. В роутере используется старое-престарое ядро 2.6.21 с uClibc 0.9.28. Первым делом, я решил воспользоваться buildroot, чтобы он и toolchain с uClibc собрал, и gdbserver статически. Отлично, все собралось, вроде запускается, однако, при отладке через IDA Pro, сервер постоянно падает, какие-то странные ошибки ptrace выдает, ну, думаю, надо пересобрать его с заголовками от ядра 2.6.21 и с uClibc 0.9.28, т.к. uClibc никогда не обещал бинарную совместимость. В интернете нашелся Ralink SDK с нужной версией ядра и uClibc. GDBServer, собранный этим toolchain, вел себя один-в-один как старый. К сожалению, в IDA Pro имеется какая-то несовместимость с gdbserver, который запущен на MIPS. К счастью, gdbserver замечательно работает с обычным gdb, собранным под mips (./configure --target mipsel-linux).Я очень редко что-то отлаживаю в голом gdb, а удобные надстройки и скрипты для него работают только с x86 и ARM. К счастью, я нашел репозиторий с .gdbinit для MIPS, и удобство отладки заметно увеличилось. Все происходило как-то так:
В конечном счете, ключ был найден всего несколькими строчками выше, чего и следовало ожидать:
После того, как я написал скрипт для расшифровки прошивки, lolipop прислал мне еще одну с похожим заголовком, но от другого роутера. Первые 4 символа (magic) у нее были R3G2. Поискав эту строку в Google, обнаружилось, что все уже сделано до нас, еще аж в начале 2013 года :(В любой расшифрованной прошивке есть строка Linux Kernel Image, которая находится всегда по одному и тому же смещению. Эта строка длиннее, чем XOR-ключ, а это значит, что нам не нужно его доставать из rc у разных производителей, а мы можем просто «найти» его из этой строки.
Но недостаточно только распаковать прошивку, нужно ее еще и запаковать, чтобы была возможность обновить ее через веб-интерфейс. Как оказалось, в функции обновления присутствует подсчет контрольной суммы прошивки:
Что выглядит на C примерно вот так:
for (i=0; i checksum = checksum + (checksum >> 16) + 0xffff;
checksum = ~(checksum + (checksum >> 16)) & 0xffff;
printf («Checksum = 0x%04X\n», checksum);
data[i] = checksum & 0xFF;
data[i+1] = (checksum >> 8) & 0xFF;
Теперь у нас есть все необходимое, чтобы заливать любые прошивки через веб-интерфейс.Ссылки
РасшифровывалкаЗашифровывалкаЗаключение
Данный способ подходит для многих устройств на SoC RT5350. Вероятно, это какой-то штатный способ обновления прошивки из SDK.Прошивки c backdoor и такого низкого качества вынуждают потребителя искать нормально работающий софт для своего устройства. lolipop добавил поддержку данного устройства в OpenWRT, и скоро ее добавят в Trunk. А еще, вчера наконец-то вышел релиз OpenWRT Barrier Breaker! (анонса на сайте еще нет).Так и живем.