Samsung WB850F стала первой камерой, объединившей в себе DRIMeIII SoC и WiFi. В ней, как и в модели EX2F, используется прошивка, в которую инженеры Samsung любезно включили файл partialImage.o.map с полным дампом компоновщика и именами всех символов из ZIP-архива. Этот их «подарок» мы используем для реверс-инжиниринга основной прошивки SoC, чтобы обеспечить прохождение проверки при обнаружении точек доступа WiFi и возможность использовать для загрузки изображений samsung-nx-emailservice.

Эта публикация продолжает тему статьи, посвящённой камерам Samsung с поддержкой WiFi, и является частью серии, освещающей линейку Samsung NX.

▍ Содержание



WB850F_FW_210086.zip — внешний контейнер


Камера WB850F относится к немногим моделям, для которых компания Samsung продолжает выпускать прошивки и вспомогательные файлы, несмотря на прекращение обслуживания приложения iLauncher.

Архив WB850F_FW_210086.zip, который можно найти на её странице, несёт в себе несколько файлов (вывод получен с помощью file):

GPS_FW/BASEBAND_FW_Flash.mbin: data
GPS_FW/BASEBAND_FW_Ram.mbin:   data
GPS_FW/Config.BIN:             data
GPS_FW/flashBurner.mbin:       data
FWUP:                          ASCII text, with CRLF line terminators
partialImage.o.map:            ASCII text
WB850-FW-SR-210086.bin:        data
wb850f_adj.txt:                ASCII text, with CRLF line terminators

Файл FWUP содержит только строку upgrade all, представляющую скрипт модуля тестирования/автоматизации. Файл wb850f_adj.txt аналогичен, но является более сложным скриптом для обновления прошивки GPS и удаления соответствующих файлов. Пока пропустим этот скрипт и каталог GPS_FW.

▍ partialImage.o.map — дамп компоновщика


Текстовый файл partialImage.o.map содержит более 300 000 строк, включая вывод компоновщика для partialImage.o и всю карту памяти линкованного файла:

output          input           virtual
section         section         address         size     file

.text                           00000000        01301444
                .text           00000000        000001a4 sysALib.o
                             $a 00000000        00000000
                        sysInit 00000000        00000000
                   L$_Good_Boot 00000090        00000000
                    archPwrDown 00000094   00000000
...
           DevHTTPResponseStart 00321a84        000002a4
            DevHTTPResponseData 00321d28        00000100
             DevHTTPResponseEnd 00321e28        00000170
...
.data                           00000000        004ed40c
                .data           00000000        00000874 sysLib.o
                         sysBus 00000000        00000004
                         sysCpu 00000004        00000004 
                    sysBootLine 00000008        00000004

Так продолжается очень долго, и это реальная карта сокровищ! Осталось лишь найти остров, для которого она составлена.

▍ WB850-FW-SR-210086.bin — анализ заголовков


Открыв WB850-FW-SR-210086.bin с помощью binwalk, мы видим длинный список заголовков файлов (HTML, PNG, JPEG, ...), заголовок VxWorks, множество путей Unix, но ничего похожего на разделы или файловые системы.

Попробуем-ка тогда сделать шестнадцатеричный дамп первого килобайта:

00000000: 3231 3030 3836 0006 4657 5f55 502f 4f4e  210086..FW_UP/ON
00000010: 424c 312e 6269 6e00 0000 0000 0000 0000  BL1.bin.........
00000020: 0000 0000 0000 0000 c400 0000 0008 0000  ................
00000030: 4f4e 424c 3100 0000 0000 0000 0000 0000  ONBL1...........
00000040: 0000 0000 4657 5f55 502f 4f4e 424c 322e  ....FW_UP/ONBL2.
00000050: 6269 6e00 0000 0000 0000 0000 0000 0000  bin.............
00000060: 0000 0000 30b6 0000 c408 0000 4f4e 424c  ....0.......ONBL
00000070: 3200 0000 0000 0000 0000 0000 0000 0000  2...............
00000080: 5b57 4238 3530 5d44 5343 5f35 4b45 595f  [WB850]DSC_5KEY_
00000090: 5742 3835 3000 0000 0000 0000 0000 0000  WB850...........
000000a0: 38f4 d101 f4be 0000 4d61 696e 5f49 6d61  8.......Main_Ima
000000b0: 6765 0000 0000 0000 0000 0000 526f 6d46  ge..........RomF
000000c0: 532f 5350 4944 2e52 6f6d 0000 0000 0000  S/SPID.Rom......
000000d0: 0000 0000 0000 0000 0000 0000 00ac f402  ................
000000e0: 2cb3 d201 5265 736f 7572 6365 0000 0000  ,...Resource....
000000f0: 0000 0000 0000 0000 4657 5f55 502f 5742  ........FW_UP/WB
00000100: 3835 302e 4845 5800 0000 0000 0000 0000  850.HEX.........
00000110: 0000 0000 0000 0000 864d 0000 2c5f c704  .........M..,_..
00000120: 4f49 5300 0000 0000 0000 0000 0000 0000  OIS.............
00000130: 0000 0000 4657 5f55 502f 736b 696e 2e62  ....FW_UP/skin.b
00000140: 696e 0000 0000 0000 0000 0000 0000 0000  in..............
00000150: 0000 0000 48d0 2f02 b2ac c704 534b 494e  ....H./.....SKIN
00000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000003f0: 0000 0000 0000 0000 0000 0000 5041 5254  ............PART

Очень интересно. Всё начинается с версии прошивки, 210086. Потом идут 0x00 0x06, сопровождаемые именем файла FW_UP/ONBL1.bin, расположенного в смещении 0x008. Следующее имя файла, FW_UP/ONBL2.bin, находится в 0x044, значит это, скорее всего, 60-байтовая запись «раздела»:

00000008: 4657 5f55 502f 4f4e 424c 312e 6269 6e00  FW_UP/ONBL1.bin.
00000018: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000028: c400 0000 0008 0000 4f4e 424c 3100 0000  ........ONBL1...
00000038: 0000 0000 0000 0000 0000 0000            ............

После имени файла идёт много нулей (составляющих 32-байтовую строку, дополненную нулями), за которой следует два целых числа, 0xc4 и 0x800, с прямым порядком байтов, сопровождаемые 20-байтовой строкой ONBL1, тоже дополненной нулями, которая, по всей видимости, представляет имя соответствующего раздела. Далее идут другие записи с аналогичной структурой. Целыми числами во второй записи (ONBL2) являются 0xb630 и 0x8c4, значит, можно предположить, что первое является длиной, а второе отражает смещение в файле (смещение одной записи всегда равно «смещение + длина» предыдущей).

Всего здесь шесть записей, значит, 0x00 0x06 между строкой версии и первой записью наверняка представляет завершающий или заполняющий байт версии прошивки и отражает количество разделов.

Понимая это, можно воссоздать таблицу разделов следующим образом:

Имя файла Размер Смещение Имя раздела
FW_UP/ONBL1.bin 196 (0xc4) 0x0000800 ONBL1
FW_UP/ONBL2.bin 46 КБ (0xb630) 0x00008c4 ONBL2
[WB850]DSC_5KEY_WB850 30 МБ (0x1d1f438) 0x000bef4 Main_Image
RomFS/SPID.Rom 48 МБ (0x2f4ac00) 0x1d2b32c Resource
FW_UP/WB850.HEX 19 КБ (0x4d86) 0x4c75f2c OIS
FW_UP/skin.bin 36 МБ (0x22fd048) 0x4c7acb2 SKIN
Теперь напишем инструмент для извлечения разделов прошивки DRIMeIII и используем его.

▍ WB850-FW-SR-210086.bin — разделы кода и данных


Этот инструмент извлекает разделы на основе их имён, добавляя в конце .bin. Выполнение file в отношении вывода здесь не особо поможет:

ONBL1.bin:      data
ONBL2.bin:      data
Main_Image.bin: OpenPGP Secret Key
Resource.bin:   MIPSEB-LE MIPS-III ECOFF executable stripped - version 0.0
OIS.bin:        data
SKIN.bin:       data

  • ONBL1 и ONBL2, похоже, представляют стадии 1 и 3 загрузчика (что подтверждается строкой в Main_Image: BootLoader(ONBL1, ONBL2) Update Done).
  • Main_Image — это сама прошивка: OpenPGP Secret Key здесь является ложным выводом; binwalk -A показывает много прологов процедур ARM, находящихся в этом файле.
  • Resource и SKIN — это крупные контейнеры, возможно, предоставленные производителем SoC для изменения скинов интерфейса камеры?
  • OIS — это не шестнадцатеричный файл, но он может являться прошивкой для отдельного оптического стабилизатора.

Из всего этого наибольший интерес представляет Main_Image.

Загрузка кода в Ghidra


Три раздела, ONBL1, ONBL2 и Main_Image, содержат фактический код ARM. Обычная прошивка ARM включает таблицу векторов сброса по адресу 0x0000000 (обычно начало флэш-памяти или ПЗУ), представляющую серию инструкций перехода. Однако все три двоичных файла в своём начале содержат фактический линейный код, значит, они наверняка должны повторно отображаться в пока неизвестный нам адрес.

Чтобы понять, как и почему камера неверно определяет точку доступа, нужно:

  1. Найти правильный адрес памяти, в который должен отображаться Main_Image.
  2. Загрузить имена символов из partialImage.o.map в Ghidra.
  3. Найти и проанализировать функцию, которая ошибочно активирует авторизацию точки доступа.

▍ Загрузка и отображение Main_Image


По умолчанию Ghidra предположит, что двоичный файл выполняет загрузку по адресу 0x0000000, и попытается проанализировать его именно таким образом. Чтобы получить корректный адрес памяти, нужно найти функцию, которая обращается к какому-то известному значению из исполняемого файла, используя абсолютный адрес. Учитывая, что всего в файле ~77 000 функций, можно начать с задачи под номером 3 и найти во вкладке Defined Strings все элементы, включающие yahoo:



Превосходно! Ghidra нашла несколько строк, которые напоминают выполнение измотанным разработчиком отладки при помощи printf. Возможно, они относятся к функции DevHTTPResponseStart(), которая, по всей видимости, проверяет, может ли камера подключиться к Yahoo, Google или Samsung:

0139f574    DevHTTPResponseStart: url=%s, handle=%x, status=%d\n, headers=%s\r\n
0139f5b8    DevHTTPResponseStart: This is YAHOO check !!!\r\n
0139f5f4    DevHTTPResponseStart: THIS IS GOOGLE/YAHOO/SAMSUNG PAGE!!!! 111\n\n\n
0139f638    DevHTTPResponseStart: 301/302/307! cannot find yahoo!  safapi_is_browser_framebuffer_on : %d , safapi_is_browser_authed(): %d  \r\n

Согласно partialImage.o.map, функция с таким именем существует по адресу 0x321a84, и Ghidra тоже нашла функцию по адресу 0x321a84. Между картой и бинарником совпадают и другие смещения функций, значит, можно предположить, что адреса .text из файла карты фактически 1:1 соответствуют Main_Image! Мы нашли остров, для которого составлялась наша карта!

Вот начало той функции:

bool FUN_00321a84(undefined4 param_1,ushort param_2,int param_3,int param_4) {
  /* удаление объявлений переменных */
  FUN_0031daec(*(DAT_00321fd4 + 0x2c),DAT_00322034,param_3,param_1,param_2,param_4);
  FUN_0031daec(*(DAT_00321fd4 + 0x2c),DAT_00322038);
  FUN_00326f84(0x68);

Она начинается с двух вызовов к FUN_0031daec() с разным числом параметров — это вновь очень напоминает отладку с помощью printf. Согласно карте памяти, это называется opd_printf()! Первый параметр отражает некий контекст или точку назначения, а второй должен быть ссылкой на строку форматирования. Два значения DAT_ опознаются Ghidra как 32-битные undefined:

DAT_00322034:
    74 35 3a c1     undefined4 C13A3574h
DAT_00322038:
    b8 35 3a c1     undefined4 C13A35B8h

Тем не менее три последних соответствующих цифры имеют совпадение в ранее встреченных отладочных строках DevHTTPResponseStart:

  • 0xc13a35740x0139f574 = 0xc0004000 (первая строка форматирования с четырьмя параметрами).
  • 0xc13a35b80x0139f5b8 = 0xc0004000 (вторая строка форматирования без параметров).

Из этого можно сделать вывод, что Main_Image должен загружаться в память по адресу 0xc0004000. Это нельзя изменить в Ghidra постфактум, поэтому нужно удалить исполняемый файл из проекта, заново его импортировать и установить желаемый базовый адрес:



▍ Загрузка имён функций из partialImage.o.map


В Ghidra есть скрипт для импорта всех меток данных и имён функций из текстовой таблицы ImportSymbolScript.py. Он ожидает, что каждая строка будет содержать три переменных, разделённых произвольным объёмом пустого пространства (в соответствии с функцией Python string.split()):

  1. Имя символа.
  2. Шестнадцатеричный адрес.
  3. f — это function, l — это label.

Карта символов имеет несколько разделов, но нас пока интересуют только функции, определённые в .text, которые в точности отображаются в адреса из Main_Image. Помимо имён функций, карта также содержит пустые строки, смещения из объектного файла (с меткой в виде .text), метки (с префиксом L$_) и локальные символы (с префиксом $).

Нам нужно ограничиться символами из раздела .text (всё после .text и до .debug_frame), избавиться от пустых строк и не-функций, затем добавить к каждому адресу 0xc0004000, чтобы обеспечить соответствие базовому адресу в Ghidra. Всё это можно в довольно непонятной форме проделать с помощью однострочной команды awk:

awk '/^\.text /{t=1;next}/^\.debug_frame /{t=0} ; !/[$.]/ { if (t && $1) { printf "%s %x f\n", $1, (strtonum("0x"$2)+0xc0004000) } }'

Или чуть понятнее при помощи более медленного цикла оболочки:

sed '1,/^\.text /d;/^\.debug_frame /,$d' | grep -v '^$' | grep -v '[.$]' | \
while read sym addr f ; do
    printf "%s %x f\n"  $sym $((0xc0004000 + 0x$addr))
done

В обоих случаях получится одинаковый вывод, который можно загрузить в Ghidra через Window / Script Manager / ImportSymbolsScript.py:

sysInit c0004000 f
archPwrDown c0004094 f
MMU_WriteControlReg c00040a4 f
MMU_WritePageTableBaseReg c00040b8 f
MMU_WriteDomainAccessReg c00040d0 f
...

▍ Реверс-инжиниринг DevHTTPResponseStart


Теперь, когда у нас есть имена функций, нужно вручную установить тип множества полей DAT_ на pointer, переименовать параметры в соответствии с отладочной строкой, после чего мы получим более-менее пригодный для использования вывод декомпилятора.

Ниже показана версия с комментариями, отредактированная для лучшей читаемости. В ней были переписаны некоторые условные выражения, а также включены ссылки на строки.

bool DevHTTPResponseStart(undefined4 handle,ushort status,char *url,char *headers) {
  bool result;
  
  opd_printf(ctx,"DevHTTPResponseStart: url=%s, handle=%x, status=%d\n, headers=%s\r\n",
      url,handle,status,headers);
  opd_printf(ctx,"DevHTTPResponseStart: This is YAHOO check !!!\r\n");
  safnotify_page_load_status(0x68);
  if ((url == NULL) || (status != 301 && status != 302 && status != 307)) {
    /* это не перенаправление по HTTP */
    if (status == 200) {
      /* HTTP 200 означает OK */
      if (headers == NULL ||
          (strstr(headers,"domain=.yahoo") == NULL &&
           strstr(headers,"Domain=.yahoo") == NULL &&
           strstr(headers,"domain=kr.yahoo") == NULL &&
           strstr(headers,"Domain=kr.yahoo") == NULL)) {
        /* при отсутствии заголовков ответа или куки yahoo проверка проваливается! */
        result = true;
      } else {
        /* в заголовках обнаружен куки yahoo */
        opd_printf(ctx,"DevHTTPResponseData: THIS IS GOOGLE/YAHOO PAGE!!!! 3333\n\n\n");
        *p_request_ongoing = 0;
        if (!safapi_is_browser_authed())
          safnotify_auth_ap(0);
        result = false;
      }
    } else if (status < 0) {
      /* отрицательный статус = отмена? */
      result = false;
    } else {
      /* положительный статус, не перенаправление, не "OK" */
      result = !safapi_is_browser_framebuffer_on();
    }
  } else {
    /* перенаправление по HTTP. */
    char *match = strstr(url,"yahoo.");
    if (match == NULL || match > (url+11)) {
      opd_printf(ctx, "DevHTTPResponseStart: 301/302/307! cannot find yahoo! safapi_is_browser_framebuffer_on : %d , safapi_is_browser_authed(): %d  \r\n",
          safapi_is_browser_framebuffer_on(), safapi_is_browser_authed());
      if (!safapi_is_browser_framebuffer_on() && !safapi_is_browser_authed()) {
        opd_printf(ctx,"DevHTTPResponseStart: 302 auth failed!!! kSAFAPIAuthErrNotAuth!! \r\n");
        safnotify_auth_ap(1);
      }
      result = false;
    } else {
      /* в URL-адресе найден элемент "yahoo." */
      opd_printf(ctx, "DevHTTPResponseStart: THIS IS GOOGLE/YAHOO/SAMSUNG PAGE!!!! 111\n\n\n");
      *p_request_ongoing = 0;
      if (!safapi_is_browser_authed())
        safnotify_auth_ap(0);
      result = false;
    }
  }
  return result;
}

▍ Интерпретация процесса обнаружения точки доступа


Итак, код в DevHTTPResponseStart проверяет выполнение одного из двух условий и вызывает safnotify_auth_ap(0), чтобы отметить точку доступа WiFi как аутентифицированную:

  1. При HTTP-ответе 200 OK сервер должен установить куки на домен .yahoo.something либо kr.yahoo.something
  2. При получении кода, связанного с перенаправлением (301/302/307), в URL-адресе (вероятно, адресе перенаправления?) рядом с началом должна находиться подстрока yahoo.
.
Если вручную перейти по запрашиваемому URL, http://www.yahoo.co.kr/, он перенаправит нас на https://www.yahoo.com/. Значит, всё в порядке?

GET / HTTP/1.1
Host: www.yahoo.co.kr

HTTP/1.1 301 Moved Permanently
Location: https://www.yahoo.com/

Не совсем. Подстрока yahoo. находится в адресе https://www.yahoo.com/ в позиции 12, но код требует, чтобы она была в одной из первых 11 позиций. Эта проверка проваливается по вине протокола TLS.

Чтобы пройти проверку точки доступа, нужно отмотать 10 лет повсеместного распространения HTTPS, либо направить запись DNS на другой сервер, который будет выполнять переадресацию на более yahoo-образное имя или устанавливать в куки домен yahoo.

После подобающего патча samsung-nx-emailservice камера начнёт подключаться и загружать фото:



▍ Итог: истинное сокровище


Этот проект позволил понять и обойти механизм подтверждения точек доступа в камере Samsung WB850F путём реверс-инжиниринга одной функции. В итоге получился крохотный патч, но найти обходной путь на основе одних только трейсов пакетов было невозможно из-за использованного инженерами Samsung «метода обнаружения» точек доступа. Когда стало ясно, что именно нужно искать, тот же обходной путь удалось применить к камерам, требовавшим подключения к MSN.com, тем самым добавив в список поддерживаемых моделей EX2F, ST200F, WB3xF и WB1100F.

Тем не менее истинное сокровище всё ещё ждёт своего искателя! Main_Image содержит более 77 000 функций, которых увлечённому охотнику за сокровищами будет предостаточно для того, чтобы лучше разобраться в принципе работы цифровых фотокамер.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (4)


  1. dlinyj
    31.05.2024 14:06
    +3

    Написанно криво (тут не к переводчику претензия, а к автору оригинальной статьи), но в целом весьма полезное чтиво для реверса.


    1. datacompboy
      31.05.2024 14:06
      +3

      Я сперва думал, что фикс был сделан для прошивки. Только в исходнике понял, что хак был на стороне эмулятора хотспота :)


  1. vvzvlad
    31.05.2024 14:06
    +6

    Так и не понял, что именно хотела сделать камера и для чего


  1. NutsUnderline
    31.05.2024 14:06
    +2

    очередное напоминание что все захаркоженое превращается в тыкву