Исследуем китайские роутеры на RT5350

в 19:06, , рубрики: backdoor, информационная безопасность, реверс-инжиниринг, Сетевое оборудование

Однажды, lolipop купил роутер на алиэкспрессе. Да не простой роутер, а очень компактный и дешевый, с 2 Ethernet-портами, USB, да еще и от фирмы, которая в начале 2000-х продавала свои mp3-плееры на территории РФ: Nexx WT1520H.
image
Стандартная прошивка, как и почти всегда, была скудная, и, конечно же, хотелось заменить ее на что-то более вменяемое. Но вот незадача — никаких альтернативных прошивок под роутер нет, и прошить непонятно как, т.к. никакие другие прошивки не принимались через веб-интерфейс, заголовок прошивки я раньше такой не видел, да и 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):

image

Т-а-а-к, теперь пускает до ввода пароля, однако стандартный пароль «admin» не принимается. Интересно. Смотрим дальше:

image

Вот так дела! Логин nexxadmin, пароль y1n2inc.com0755, с доступом через WAN.
В прошивке есть mtd_write, так что ничто не мешает нам уже сейчас залить OpenWRT прямо на флеш, что и было сделано lolipop, но все же было интересно разреверсить алгоритм шифрования. К сожалению, моих навыков ассемблирования MIPS в голове явно недостаточно, и я испытывал большой дискомфорт только смотря на весь этот код, поэтому я заказал себе такой же роутер, и, о чудо, через 2 месяца он был у меня.

Продолжаем исследование

image
Подключен только RX, земля общая с лаптопом по USB-питанию.

Обновить роутер можно как через веб-интерфейс, так и по tftp (который, опять же, слушает WAN!). Tftp-демон (upgraded из rc), похоже, сломан, т.к. обновление через него не приводило к обновлению прошивки в роутере, хотя и никаких ошибок не было. Следует заметить, что обновление прошивки по tftp требует аутентификации с таким же паролем, как и на веб-интерфейс, так что тяжело назвать его backdoor, скорее просто неправильно сконфигурированный сервис.

Я решил исследовать обновление прошивки именно через tftp.
Простое обновление через стандартный tftp-клиент приводило к «Upgrade not possible: Incorrect Password» от upgraded. Давайте заглянем в него:

image

Обычные клиенты, похоже, не умеют отправлять 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, выглядит она как-то так:

image

Найти ключ в статике я не смог, поэтому я приступил к отладке. Чтобы отлаживать что-то на устройстве, разумеется, нужно сначала собрать отладчик. 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, и удобство отладки заметно увеличилось. Все происходило как-то так:

image

В конечном счете, ключ был найден всего несколькими строчками выше, чего и следовало ожидать:

image

После того, как я написал скрипт для расшифровки прошивки, lolipop прислал мне еще одну с похожим заголовком, но от другого роутера. Первые 4 символа (magic) у нее были R3G2. Поискав эту строку в Google, обнаружилось, что все уже сделано до нас, еще аж в начале 2013 года :(
В любой расшифрованной прошивке есть строка Linux Kernel Image, которая находится всегда по одному и тому же смещению. Эта строка длиннее, чем XOR-ключ, а это значит, что нам не нужно его доставать из rc у разных производителей, а мы можем просто «найти» его из этой строки.

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

image

Что выглядит на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js