三星WB850F 相机固件逆向(译)

IoT 4周前 admin
65 0 0

Georg Lukas, 2024-05-24 17:30

三星的WB850F紧凑型相机是首个结合了DRIMeIII SoC和WiFi的型号。与EX2F一起,它具备一个未压缩的固件二进制文件,三星在固件ZIP中贴心地添加了一个partialImage.o.map文件,其中包含了完整的链接器转储和所有符号名称。我们正在利用这份礼物来逆向工程主SoC固件,以便我们可以让相机通过WiFi热点检测并使用samsung-nx-emailservice。

这是对三星WiFi相机文章的后续,也是三星NX系列的一部分。

  1. [WB850F_FW_210086.zip – 外层容器]
    1. [partialImage.o.map – 链接器转储]
    2. [WB850-FW-SR-210086.bin – 头文件分析]
    3. [WB850-FW-SR-210086.bin – 代码和数据分区]
  2. [在Ghidra中加载代码]
    1. [加载和映射Main_Image]
    2. [从partialImage.o.map加载函数名称]
    3. [逆向工程DevHTTPResponseStart](
    4. [解释热点检测]
  3. [总结:真正的宝藏]

WB850F_FW_210086.zip – 外层容器

WB850F是三星在停止iLauncher应用程序后仍然发布firmware and support files的少数型号之一。

我们可以从那里获取的WB850F_FW_210086.zip存档包含了相当多的文件(由file识别):

GPS_FW/BASEBAND_FW_Flash.mbin: 数据
GPS_FW/BASEBAND_FW_Ram.mbin:   数据
GPS_FW/Config.BIN:             数据
GPS_FW/flashBurner.mbin:       数据
FWUP:                          ASCII 文本,使用 CRLF 行终止符
partialImage.o.map:            ASCII 文本
WB850-FW-SR-210086.bin:        数据
wb850f_adj.txt:                ASCII 文本,使用 CRLF 行终止符

FWUP文件只包含字符串upgrade all,这是一个固件测试/自动化模块的脚本。wb850f_adj.txt文件是一个类似但更复杂的脚本,用于升级GPS固件并删除相应的文件。现在让我们暂时跳过与GPS相关的脚本和GPS_FW文件夹。

partialImage.o.map – 链接器转储

partialImage.o.map是一个文本文件,包含超过300k行,包含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 – 头文件分析

使用binwalk查看WB850-FW-SR-210086.bin会得到一个长长的文件头列表(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,紧接着在偏移量0x008处是FW_UP/ONBL1.bin,这非常像一个文件名。下一个文件名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字节的零填充字符串),接着是两个小端整数0xc40x800,然后是一个20字节的零填充字符串ONBL1,这可能是相应的分区名称。之后,相同结构的下一条记录跟随其后。第二条记录中的整数(ONBL2)是0xb6300x8c4,因此我们可以假设第一个数字是长度,第二个数字是文件中的偏移量(一条记录的偏移量总是前一条记录的偏移量加长度)。

总共有六条记录,所以版本字符串和第一条记录之间的0x00 0x06可能是固件版本号的终止或填充字节,以及分区数量的一字节数字。

有了这些知识,我们可以如下重建分区表:

File name size offset partition name
FW_UP/ONBL1.bin 196 (0xc4) 0x0000800 ONBL1
FW_UP/ONBL2.bin 46 KB (0xb630) 0x00008c4 ONBL2
[WB850]DSC_5KEY_WB850 30 MB (0x1d1f438) 0x000bef4 Main_Image
RomFS/SPID.Rom 48 MB (0x2f4ac00) 0x1d2b32c Resource
FW_UP/WB850.HEX 19 KB (0x4d86) 0x4c75f2c OIS
FW_UP/skin.bin 36 MB (0x22fd048) 0x4c7acb2 SKIN

让我们编写一个提取DRIMeIII固件分区的工具,并使用它!

WB850-FW-SR-210086.bin – 代码和数据分区

该工具根据分区名称提取分区,并分别添加".bin"后缀。在输出上运行file并没有太大帮助:

ONBL1.bin:      数据 ONBL2.bin:      数据 Main_Image.bin: OpenPGP 秘密密钥 Resource.bin:   MIPSEB-LE MIPS-III ECOFF 可执行文件剥离 – 版本 0.0 OIS.bin:        数据 SKIN.bin:       数据

  • ONBL1ONBL2可能是引导程序的第一阶段和第二阶段(由Main_Image中的字符串确认:"BootLoader(ONBL1, ONBL2) Update Done")。

  • Main_Image是实际的固件:OpenPGP 秘密密钥是错误的阳性,binwalk -A报告此文件中相当多的ARM函数序言。

  • ResourceSKIN是相当大的容器,可能是由SoC制造商提供,用于“换肤”相机用户界面?

  • OIS并不真的是其文件名所声称的十六进制,但它可能是专用光学图像稳定器的固件。

在所有这些中,Main_Image是最有趣的一个。

在Ghidra中加载代码

ONBL1ONBL2Main_Image三个分区包含实际的ARM代码。典型的ARM固件将在地址0x0000000(通常是闪存/只读存储器的开头)包含重置向量表,这是一系列跳转指令。然而,所有三个二进制文件在其各自的开头都包含实际的线性代码,所以它们很可能需要重新映射到某个未知的地址。

为了弄清楚相机如何错误地检测热点,我们需要:

  1. 找到正确的内存地址来映射Main_Image
  2. partialImage.o.map中的符号名称加载到Ghidra中
  3. 找到并分析错误触发热点登录的函数

加载和映射Main_Image

默认情况下,Ghidra会假设二进制文件加载到地址0x0000000并尝试这样分析它。要获得正确的内存地址,我们需要找到一个使用绝对地址访问二进制文件中已知值的函数。鉴于有77k个函数,我们可以从接近任务#3的东西开始,在Ghidra的“已定义字符串”标签中搜索"yahoo"

三星WB850F 相机固件逆向(译)

Ghidra截图,包含一些Yahoo! 字符串

太好了!Ghidra识别了一些看起来像一个恼怒的开发者的printf调试的字符串,可能来自一个名为DevHTTPResponseStart()的函数,它似乎是检查相机是否能够正确访问Yahoo、Google或Samsung的函数:

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

根据partialImage.o.map,实际存在一个名为该地址0x321a84的函数,Ghidra也在0x321a84找到了一个函数。在映射和二进制文件之间还有一些其他匹配的函数偏移量,所以我们可以假设映射文件中的.text地址实际上与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位未定义值:

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

然而,相应的后三位数字与前面遇到的"DevHTTPResponseStart: "调试字符串相匹配:

  • 0xc13a3574 - 0x0139f574 = 0xc0004000(带有四个参数的第一个格式字符串)
  • 0xc13a35b8 - 0x0139f5b8 = 0xc0004000(第二个不带参数的格式字符串)

从这一点我们可以合理地得出结论,Main_Image需要加载到内存地址0xc0004000。这在Ghidra中无法事后更改,因此我们需要从项目中移除二进制文件,重新导入它,并相应地设置基地址:

三星WB850F 相机固件逆向(译)

Ghidra导入选项对话框的截图

partialImage.o.map 加载函数名称

Ghidra 有一个脚本可以批量导入文本表中的数据标签和函数名称,ImportSymbolScript.py。它期望每行包含三个变量,由任意数量的空格分隔(由 Python 的 string.split() 确定):

  1. 符号名称
  2. (十六进制)地址
  3. 函数为 “f” 或标签为 “l”

我们的符号映射包含多个部分,但我们目前只对 .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 fn", $1, (strtonum("0x"$2)+0xc0004000) } }'

或者用一个稍微不那么隐秘但速度慢得多的 shell 循环:

sed '1,/^.text /d;/^.debug_frame /,$d' | grep -v '^$' | grep -v '[.$]' | 
while read sym addr f ; do
    printf "%s %x fn"  $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=%dn, headers=%srn",
      url, handle, status, headers);
  opd_printf(ctx, "DevHTTPResponseStart: This is YAHOO check !!!rn");
  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 cookie --> 检查失败! */
        result = true;
      } else {
        /* 我们在 headers 中找到了 yahoo cookie 位 */
        opd_printf(ctx, "DevHTTPResponseData: THIS IS GOOGLE/YAHOO PAGE!!!! 3333rnrnrn");
        *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  rn",
          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!! rn");
        safnotify_auth_ap(1);
      }
      result = false;
    } else {
      /* 在 url 中找到了 "yahoo." */
      opd_printf(ctx, "DevHTTPResponseStart: THIS IS GOOGLE/YAHOO/SAMSUNG PAGE!!!! 111rnrnrn");
      *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*" 上设置 cookie。

  2. 在 HTTP 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." 在 URL "https://www.yahoo.com/" 的位置 12,但代码要求它在前 11 个位置之一。这个检查已经被 TLS 杀死了!

要通过热点检查,我们必须倒退十年的 HTTPS-everywhere,或者将 DNS 记录指向一个不同的服务器,该服务器要么 HTTP-redirect 到一个不同的、更像 yahoo 的名称,要么在 yahoo 域上设置 cookie。

在相应地修补 samsung-nx-emailservice之后,相机实际上会连接并上传照片:

三星WB850F 相机固件逆向(译)

WB850F 发送照片

总结:真正的宝藏

这次深入研究使我们能够理解并绕过基于逆向工程函数的三星 WB850F WiFi 相机的热点检测。结果的补丁很小,但由于三星工程师实施的“检测方法”,仅凭数据包跟踪猜测解决方法是不可能的。一旦知道要寻找什么,同样的解决方法也应用于请求 MSN.com 的相机,因此在支持相机列表中也增加了 EX2F, ST200F, WB3xF 和 WB1100F。

然而,真正的宝藏仍在等待!Main_Image 包含超过 77k 个函数,所以对于一个好奇的寻宝者来说,有足够的东西可以探索,以便更好地了解数码相机的工作原理。

– END –


原文始发于微信公众号(3072):三星WB850F 相机固件逆向(译)

版权声明:admin 发表于 2024年5月26日 下午5:36。
转载请注明:三星WB850F 相机固件逆向(译) | CTF导航

相关文章