Однажды, 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<f.len-1; i+=2)
{
checksum += (data[i+1] << 8 ) | data[i];
}
if (i < f.len)
{
checksum += data[i];
printf("Got odd byte: 0x%02Xn", data[i]);
i+=1;
}
checksum = checksum + (checksum >> 16) + 0xffff;
checksum = ~(checksum + (checksum >> 16)) & 0xffff;
printf("Checksum = 0x%04Xn", checksum);
data[i] = checksum & 0xFF;
data[i+1] = (checksum >> 8) & 0xFF;
Теперь у нас есть все необходимое, чтобы заливать любые прошивки через веб-интерфейс.
Ссылки
Расшифровывалка
Зашифровывалка
Заключение
Данный способ подходит для многих устройств на SoC RT5350. Вероятно, это какой-то штатный способ обновления прошивки из SDK.
Прошивки c backdoor и такого низкого качества вынуждают потребителя искать нормально работающий софт для своего устройства. lolipop добавил поддержку данного устройства в OpenWRT, и скоро ее добавят в Trunk. А еще, вчера наконец-то вышел релиз OpenWRT Barrier Breaker! (анонса на сайте еще нет).
Так и живем.
Автор: ValdikSS