Georg Lukas, 2025-04-30 17:51

In 2014 and 2015, Samsung released the NX mini and NX3000/NX3300 cameras as part of their mirrorless camera line-up. My 2023 archaeological expedition showed that they use the Fujitsu M7MU SoC, which also powers the camera in the dual-SoC Exynos+M7MU Galaxy K-Zoom. This blog post performs a detailed step-by-step reverse engineering of the firmware file format. It will be followed by a similar post about the LZSS-based compression mechanism, in order to obtain the raw firmware image for actual code analysis.

The TL;DR results of this research can be found in the project wiki: M7MU Firmware Format / SF_RESOURCE.

Prelude

Two years ago I did a heritage analysis of all NX models and found some details about the history of the Milbeaut MB86S22A SoC powering the above models. The few known details can be read up in that post.

Copyright (c) 2<80>^@^@5-2011, Jouni Ma^@^@linen <*@**.**>
^@^@and contributors^@^B^@This program ^@^Kf^@^@ree software. Yo!
u ^@q dis^C4e it^AF/^@<9c>m^D^@odify^@^Q
under theA^@ P+ms of^B^MGNU Gene^A^@ral Pub^@<bc> License^D^E versPy 2.

The firmware files are using some sort of compression that neither I nor binwalk knew about, so the further analysis was stalled. Until April 2025. Nina wrote a fascinating thread about the TRON operating system, I chimed in with a shameless plug of my own niche knowledge of Β΅ITRON on Samsung cameras, and got Igor Skochinsky nerd-sniped. Igor quickly realized it is a variant of LZSS, similar to a reverse-engineered HP firmware.

Together, we went on a three-week journey of puzzles within puzzles. This post is the cleaned up documentation of the first part of that treasure hunt, hoping to inspire and guide other reverse engineers.

Collecting .bin files

To analyze the format it's helpful to obtain as many diverse specimen as possible. Samsung still offers the latest camera firmware versions: NX mini 1.10, NX3000 1.11, NX3300 1.01. Older versions can be obtained from the NX Files archive. The Galaxy K Zoom firmware can be downloaded from portals like SamFw. The interesting part is stored in the sparse ext4 root filesystem as /vendor/firmware/RS_M7MU.bin. With only 6.2MB it's the smallest specimen, the dedicated camera files are over 100MB each.

The details of the header format were "discovered" back in 2023 by doing a github search for "M7MU", and finding an Exynos interface driver. The driver documents the header format that matches all known specimen.

The header has three interesting parts for further analysis (the values and hex-dumps in this blog post are all taken from DATANXmini.bin version 1.10; the header values are little-endian):

  • the "writer" (writer_load_size = 0x4fc00 and write_code_entry 0x40000400)
  • the "code" (code_size = 0xafee12 and offset_code = 0x50000)
  • the "sections"

The section_info field is an array of 25 integers, the first one looking like a count, and the following ones like tuples of [number, size] (we can rule out [number, offset] because the second column is not growing linearly):

section_info = 00000007
          00000001 0050e66c
          00000002 001a5985
          00000003 00000010
          00000004 00061d14
          00000005 003e89d6
          00000006 00000010
          00000007 00000010
           10x 00000000

Adding up the sizes of all sections gives us 0x958d86 or roughly 9.3MB.

The writer

The writer is an uncompressed 320KB ARM binary module. The load address of 0x40000400 and the header size of 1024 = 0x400 imply that the loader starts right after the header. A brief analysis indicates code to access a exFAT, FAT and SDIO. This seems to be the module that does a full copy of the firmware image from an SD card to internal flash, but without actually uncompressing it.

The writer also seems to end before 0x50000 = 0x400 + 0x4fc00, padded with 47KB of zero bytes:

00044270: 04f0 1fe5 280a 0040 0000 0000 0000 0000  ....(..@........
00044280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00050000: 5a7d 0000 f801 9fe5 0010 90e5 c010 81e3  Z}..............
00050010: 0010 80e5 5004 ec04 1040 0410 100f 11ee  ....P....@......

The code

The above hex-dump also shows that something new begins at 0x50000, matching the offset_code header value. Assuming that it's the code block and that it's ~11MB (code_size = 0xafee12) we can check for its end as well, at 0xb4ee12:

00b4ec10: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00b4ee00: 800c 0100 0000 0200 0000 0300 0000 0000  ................
              ╭────────────────────────────────────────────────────
00b4ee10: ed08β”‚0000 0000 0000 0000 0000 0000 0000  ................
──────────────╯
00b4ee20: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00b4f000: 5346 5f52 4553 4f55 5243 4500 0000 0000  SF_RESOURCE.....

This is also a match, there is a bunch of zero-padding within the code block, and it ends with 0xed 0x08, followed by some more zero-padding after the code block.

Surprise SF_RESOURCE chunk

The just discovered block at 0xb4f000 looks like some sort of resource section. Again, it's not directly known to binwalk (but binwalk finds a number of known signatures within!). Let's investigate how it continues:

00b4f000: 5346 5f52 4553 4f55 5243 4500 0000 0000  SF_RESOURCE.....
00b4f010: 3031 2e30 a300 0000 0000 0000 0000 0000  01.0............
00b4f020: 4e58 4d49 4e49 2e48 4558 0000 0000 0000  NXMINI.HEX......
00b4f030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f050: 0000 0000 0000 0000 0000 0000 0c92 0000  ................
00b4f060: 6364 2e69 736f 0000 0000 0000 0000 0000  cd.iso..........
00b4f070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f090: 0000 0000 0000 0000 00c0 0000 00f8 0600  ................
00b4f0a0: 4951 5f43 4150 2e42 494e 0000 0000 0000  IQ_CAP.BIN......
00b4f0b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f0c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b4f0d0: 0000 0000 0000 0000 00c0 0700 8063 0000  .............c..
    <snipped a looong list of file headers>
00b518a0: 6c63 645f 6372 6f73 732e 6a70 6700 0000  lcd_cross.jpg...
00b518b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b518c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00b518d0: 0000 0000 0000 0000 00c0 5507 6d3d 0100  ..........U.m=..
    <end of the file headers? the following is not a filename>
00b518e0: 2001 0481 0000 005a 0000 7ff8 0000 014d   ......Z.......M
00b518f0: 0000 015b 0000 015d 0000 0000 0000 0000  ...[...]........

We have an obvious magic string (SF_RESOURCE), followed by a slightly weird version string ("01.0"), an 0xa3 byte and some zeroes to align to the next 32 bytes.

Then comes what looks like a file system with "NXMINI.HEX", "cd.iso", "IQ_CAP.BIN" etc...

Each file seems to have a 64-byte header, starting with the filename and ending with some numbers. The first filename is at 0xb4f020, the first non-filename is at 0xb518e0, giving us (0xb518e0 - 0xb4f020)/64 = 163 = 0xa3 files, and confirming that the header contains the number of files in the resource section. Given that the header numbers are little-endian, the number of files is probably not just one byte, but maybe two or four.

The numbers in each file header seem to be two little-endian integers, with the first one growing linearly (0x0, 0xc000, 0x7c000, ... 0x755c000), and the second one varying (0x920c, 0x6f800, 0x6380, ... 0x13d6d).

From that we can assume that the first number is the offset of the file, relative to the end of the file headers (first one is 0), and the second value is most probably the respective size. We can transfer this knowledge into a tool to print and dump the resouce section, sfresource.py:

Filename Offset Size Filename Offset Size
NXMINI.HEX 0x00000000 37388 cd.iso 0x0000c000 456704
IQ_CAP.BIN 0x0007c000 25472 IQ_COMM.BIN 0x00084000 60
IQ_M_FHD.BIN 0x00088000 24784 IQ_M_HD.BIN 0x00090000 24784
IQ_M_SD.BIN 0x00098000 26096 IQ_V_FHD.BIN 0x000a0000 24784
IQ_V_HD.BIN 0x000a8000 24784 IQ_V_SD.BIN 0x000b0000 26096
cac_par1.BIN 0x000b8000 4896 cac_par2.BIN 0x000bc000 4980
cac_par3.BIN 0x000c0000 4980 cac_par4.BIN 0x000c4000 4980
cac_par5.BIN 0x000c8000 4980 COMMON.BIN 0x000cc000 58
CAPTURE.BIN 0x000d0000 57004 dt_bg.jpg 0x000e0000 117219
file_ng.jpg 0x00100000 16356 logo.bin 0x00104000 307200
wifi_bg.yuv 0x00150000 691200 mplay_bg.jpg 0x001fc000 177465
PRD_CMD.XML 0x00228000 15528 res.dat 0x0022c000 85463552
Hdmi_res.dat 0x053b0000 4492288 Hdmi_f_res.dat 0x057fc000 3332608
pa_1.jpg 0x05b2c000 548076 pa_1p.jpg 0x05bb4000 113106
pa_2.jpg 0x05bd0000 275490 pa_2p.jpg 0x05c14000 51899
pa_3.jpg 0x05c24000 283604 pa_3p.jpg 0x05c6c000 73704
pa_4.jpg 0x05c80000 308318 pa_4p.jpg 0x05ccc000 85517
pa_5.jpg 0x05ce4000 151367 pa_5p.jpg 0x05d0c000 37452
pa_6.jpg 0x05d18000 652185 pa_6p.jpg 0x05db8000 101948
pa_7.jpg 0x05dd4000 888479 pa_7p.jpg 0x05eb0000 152815
Cross.raw 0x05ed8000 617100 Fisheye2.jpg 0x05f70000 114174
Fisheye1.raw 0x05f8c000 460800 Fisheye3.bin 0x06000000 1200
HTone3.raw 0x06004000 76800 HTone5.raw 0x06018000 76800
HTone10.raw 0x0602c000 76800 Min320.raw 0x06040000 19200
Min640.raw 0x06048000 38400 Min460.raw 0x06054000 63838
Movie_C1.jpg 0x06064000 401749 Movie_C2.jpg 0x060c8000 277949
Movie_C3.jpg 0x0610c000 311879 Movie_V1.raw 0x0615c000 307200
Movie_V2.raw 0x061a8000 307200 Movie_V3.raw 0x061f4000 307200
Movie_R0.raw 0x06240000 1555200 Movie_R1.raw 0x063bc000 388800
Sketch0.raw 0x0641c000 1443840 Sketch1.raw 0x06580000 322560
VignetC.jpg 0x065d0000 191440 VignetV.raw 0x06600000 460800
VignetV_PC.raw 0x06674000 614400 FD_RSC1 0x0670c000 1781664
BD_RSC1 0x068c0000 28188 ED_RSC1 0x068c8000 323628
SD_RSC1 0x06918000 270508 OLDFILM1.JPG 0x0695c000 154647
OLDFILM2.JPG 0x06984000 158531 OLDFILM3.JPG 0x069ac000 166034
OLDFILM4.JPG 0x069d8000 170281 OLDFILM5.JPG 0x06a04000 169271
BS_POW1.wav 0x06a30000 104060 BS_POW2.wav 0x06a4c000 109046
BS_POW3.wav 0x06a68000 94412 BS_MOVE.wav 0x06a80000 4678
BS_MOVE2.wav 0x06a84000 5032 BS_MENU.wav 0x06a88000 25340
BS_SEL.wav 0x06a90000 3964 BS_OK.wav 0x06a94000 3484
BS_TOUCH.wav 0x06a98000 5340 BS_DEPTH.wav 0x06a9c000 4904
BS_CANCL.wav 0x06aa0000 17708 BS_NOBAT.wav 0x06aa8000 194228
BS_NOKEY.wav 0x06ad8000 13308 BS_INFO.wav 0x06adc000 12168
BS_WARN.wav 0x06ae0000 20768 BS_CONN.wav 0x06ae8000 88888
BS_UNCON.wav 0x06b00000 44328 BS_REC1.wav 0x06b0c000 26632
BS_REC2.wav 0x06b14000 47768 BS_AF_OK.wav 0x06b20000 10612
BS_SHT_SHORT.wav 0x06b24000 4362 BS_SHT_SHORT_5count.wav 0x06b28000 35870
BS_SHT_SHORT_30ms.wav 0x06b34000 1978 BS_SHT_Conti_Normal.wav 0x06b38000 44236
BS_SHT_Conti_6fps.wav 0x06b44000 22904 BS_SHT1.wav 0x06b4c000 63532
BS_SHT_Burst_10fps.wav 0x06b5c000 552344 BS_SHT_Burst_15fps.wav 0x06be4000 375752
BS_SHT_Burst_30fps.wav 0x06c40000 257624 BS_COUNT.wav 0x06c80000 2480
BS_2SEC.wav 0x06c84000 87500 BS_SHT_LONG_OPEN.wav 0x06c9c000 16000
BS_SHT_LONG_CLOSE.wav 0x06ca0000 15992 BS_MC1.wav 0x06ca4000 13944
BS_FACE1.wav 0x06ca8000 36428 BS_FACE2.wav 0x06cb4000 36048
BS_FACE3.wav 0x06cc0000 3428 BS_JINGLE.wav 0x06cc4000 218468
BS_MEW.wav 0x06cfc000 208920 BS_DRIPPING.wav 0x06d30000 102244
BS_TIMER.wav 0x06d4c000 30188 BS_TIMER_2SEC.wav 0x06d54000 381484
BS_TIMER_3SEC.wav 0x06db4000 278956 BS_ROTATION.wav 0x06dfc000 5316
BS_NFC_START.wav 0x06e00000 124714 BS_TEST.wav 0x06e20000 24222
im_10_1m.bin 0x06e28000 123154 im_13_3m.bin 0x06e48000 134802
im_16_9m.bin 0x06e6c000 191378 im_1_1m.bin 0x06e9c000 10578
im_20m.bin 0x06ea0000 238290 im_2m.bin 0x06edc000 27218
im_2_1m.bin 0x06ee4000 23570 im_4m.bin 0x06eec000 40338
im_4_9m.bin 0x06ef8000 57618 im_5m.bin 0x06f08000 65682
im_5_9m.bin 0x06f1c000 76114 im_7m.bin 0x06f30000 69906
im_7_8m.bin 0x06f44000 92178 im_vga.bin 0x06f5c000 3474
set_bg.jpg 0x06f60000 13308 DV_DSC.jpg 0x06f64000 18605
DV_DSC.png 0x06f6c000 2038 DV_DSC_S.jpg 0x06f70000 12952
DV_DSC_S.png 0x06f74000 740 DEV_NO.jpg 0x06f78000 29151
wifi_00.bin 0x06f80000 13583 wifi_01.bin 0x06f84000 66469
wifi_02.bin 0x06f98000 87936 wifi_03.bin 0x06fb0000 63048
wifi_04.bin 0x06fc0000 113645 wifi_05.bin 0x06fdc000 172
wifi_06.bin 0x06fe0000 12689 wifi_07.bin 0x06fe4000 12750
wifi_08.bin 0x06fe8000 3933 cNXMINI.bin 0x06fec000 2048
net_bg0.jpg 0x06ff0000 7408 net_bg2.jpg 0x06ff4000 7409
net_bg3.jpg 0x06ff8000 7409 qwty_bg.jpg 0x06ffc000 10953
net_bg0.yuv 0x07000000 691200 net_bg2.yuv 0x070ac000 691250
net_bg3.yuv 0x07158000 691208 qwty_bg.yuv 0x07204000 691200
ChsSysDic.dic 0x072b0000 1478464 ChsUserDic.dic 0x0741c000 31744
ChtSysDic.dic 0x07424000 1163484 ChtUserDic.dic 0x07544000 31744
lcd_grad_cir.jpg 0x0754c000 26484 lcd_grad_hori.jpg 0x07554000 32586
lcd_cross.jpg 0x0755c000 81261

The JPEG files are backgrounds and artistic effects, the WAV files are shutter, timer and power-on/off effects. cd.iso is the i-Launcher install CD that the camera emulates over USB. PRD_CMD.XML is a structured list of "Production Mode System Functions":

<!--Production Mode System Functions-->
<pm_system>
    <!------------Key Command-------------->
    <key cmd_id="0x1">
        <s1 index_id="0x1">s1</s1>
        <s2 index_id="0x2">s2</s2>
        <menu index_id="0x3">menu</menu>
        ...
        <ft_mode index_id="0x11">ft_mode</ft_mode>
        <ok_ng index_id="0x12">ok_ng</ok_ng>
    </key>
    <!------------Touch Command-------------->
    <touch cmd_id="0x2">
        <mask index_id="0x1">mask</mask>
        <unmask index_id="0x2">unmask</unmask>
    </touch>
    ...
</pm_system>

The last file ends at 0xb518e0 + 0x755c000 + 81261 = 0x80c164d - can we find more surprise sections after that?

                                          ╭─────────────────────────
080c1640: 1450 0145 0014 5001 4500 7fff d9β”‚00 0000  .P.E..P.E.......
──────────────────────────────────────────╯
080c1650: 0000 0000 0000 0000 0000 0000  0000 0000  ................
*
080c18e0: c075 12cf e018 0c08 fc00 0000  0000 0000  .u..............
080c18f0: 0000 0000                                 ....
<EOF>

There is some more padding and an unknown 9-byte value. It might be a checksum, verification code or similar. We can probably ignore that for now.

The SF_RESOURCE chunk without this unknown "checksum" is 0x80c164d - 0xb4f000 bytes, or ~117MB.

The code sections

The section_info variable was outlining some sort of partitioning. So far we have found the writer (320KB), the code block (11MB) and the SF_RESOURCE chunk (117MB) in the .bin file. There is no space in the .bin to fit another 9.3MB, unless it is within one of the already-identified parts.

Given that the "code" part is 11MB and the sections are 9.3MB, they might actually fit into the code part. Let's see what is at offset_code + section[1].size = 0x50000 + 0x50e66c = 0x55e66c:

                                       ╭───────────────────────────
0055e660: 58b7 33e1 1f00 8000 0000 0000β”‚0000 0000  X.3.............
───────────────────────────────────────╯
0055e670: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
0055e800: 6ab3 0000 70b4 022b 08bf 5200 002a 4ff0  j...p..+..R..*O.

Okay, there is actual data and some zeroes, then 404 zero bytes until some more data comes. Apparently those 404 bytes are padding the first section to some alignment boundary - maybe it's block_size = 0x400 from the header?

At 0x55e800 + section[2].size = 0x704185 there is a similar picture of trailing zeroes within the expected section, followed by zero padding:

                      ╭─────────────────────────────────────────────
00704180: 0100 0000 00β”‚00 0000 0000 0000 0000 0000  ................
──────────────────────╯
00704190: 0000 0000  0000 0000 0000 0000 0000 0000  ................
*
00704200: 800c 7047  0000 0300 0000 0000 0000 0000  ..pG............
────────────────────────────────────────────────────────────────────
00704210: 0000 0000  0000 0000 0000 0000 0000 0000  ................
*
00704400: 4efb 0001  10b5 7648 a1f6 20de 75a0 a1f6  N.....vH.. .u...

Hovever, 0x704200 is not divisible by 0x400, so we need to correct our assumptions on the section alignment. Section #3 at 0x704200 is only 0x10 = 16 bytes, and is followed by the next section at 0x704400, giving us an effective alignment of 0x200 bytes.

In total, we end up with seven section as follows, and we can extend m7mu.py with the -x argument to extract all partitions (even including the writer and the resources):

Offset Size Section
0x050000 5301868 chunk-01.bin
0x55e800 1726853 chunk-02.bin
0x704200 16 chunk-03.bin
0x704400 400660 chunk-04.bin
0x766200 4098518 chunk-05.bin
0xb4ec00 16 chunk-06.bin
0xb4ee00 16 chunk-07.bin

Stay tuned for the next part of the series, where we will find out how the compression of the seven chunks works.


Discuss on Mastodon

Discuss on HN