Article image.

Analyzing Flash Partitions

Brian Murphy @ 2024-03-15 04:31:14

I have a dump of one of the flash partitions of my recently purchased Zyxel GS1900-24HPv2. With that in hand, I will be able to continue the analysis of how this switch works. Will this JFFS2 filesystem image contain the missing scripts that are needed for Linux to boot on this switch?

jefferson mtdblock binwalk zyxel journalling mtd-utils jffs2

Intro


At the end of the previous article, Dumping the Flash, I had successfully dumped the /dev/mtdblock3 to an image file. In theory, this is just a JFFS2 filesystem image that can be mounted and checked out. Once mounted, I will be able to see the contents of /mnt/startup-config.

Hopefully this file will offer the next clues to how this firmware works.

To poke around at this filesystem I will once again turn to binwalk, and introduce a second tool called Jefferson.


 

Let's take a quick look at what is in the image.

binwalk -B jffs2_cfg.img

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JFFS2 filesystem, big endian
500           0x1F4           Zlib compressed data, compressed
2004          0x7D4           Zlib compressed data, compressed
2544          0x9F0           Zlib compressed data, compressed
3284          0xCD4           Zlib compressed data, compressed
3976          0xF88           Zlib compressed data, compressed
5488          0x1570          Zlib compressed data, compressed
6540          0x198C          Zlib compressed data, compressed
7144          0x1BE8          Zlib compressed data, compressed
7332          0x1CA4          Zlib compressed data, compressed
7628          0x1DCC          Zlib compressed data, compressed
8436          0x20F4          Zlib compressed data, compressed
9248          0x2420          Zlib compressed data, compressed
10112         0x2780          Zlib compressed data, compressed
10864         0x2A70          Zlib compressed data, compressed
11616         0x2D60          Zlib compressed data, compressed
12480         0x30C0          Zlib compressed data, compressed
13212         0x339C          Zlib compressed data, compressed
13944         0x3678          Zlib compressed data, compressed
14788         0x39C4          Zlib compressed data, compressed
15528         0x3CA8          Zlib compressed data, compressed
16268         0x3F8C          Zlib compressed data, compressed
17120         0x42E0          Zlib compressed data, compressed
17860         0x45C4          Zlib compressed data, compressed
18600         0x48A8          Zlib compressed data, compressed
19452         0x4BFC          Zlib compressed data, compressed
20192         0x4EE0          Zlib compressed data, compressed
20932         0x51C4          Zlib compressed data, compressed
21648         0x5490          JFFS2 filesystem, big endian
22324         0x5734          JFFS2 filesystem, big endian
29756         0x743C          Zlib compressed data, compressed
30428         0x76DC          JFFS2 filesystem, big endian
30552         0x7758          Zlib compressed data, compressed
31224         0x79F8          JFFS2 filesystem, big endian
31352         0x7A78          Zlib compressed data, compressed
32204         0x7DCC          Zlib compressed data, compressed
32944         0x80B0          Zlib compressed data, compressed
33684         0x8394          Zlib compressed data, compressed
34536         0x86E8          Zlib compressed data, compressed
35276         0x89CC          Zlib compressed data, compressed
36016         0x8CB0          Zlib compressed data, compressed
36868         0x9004          Zlib compressed data, compressed
37608         0x92E8          Zlib compressed data, compressed
38348         0x95CC          Zlib compressed data, compressed
39200         0x9920          Zlib compressed data, compressed
39940         0x9C04          Zlib compressed data, compressed
40680         0x9EE8          Zlib compressed data, compressed
41532         0xA23C          Zlib compressed data, compressed
42272         0xA520          Zlib compressed data, compressed
43012         0xA804          Zlib compressed data, compressed
43864         0xAB58          Zlib compressed data, compressed
44604         0xAE3C          Zlib compressed data, compressed
45344         0xB120          Zlib compressed data, compressed
46196         0xB474          Zlib compressed data, compressed
46936         0xB758          Zlib compressed data, compressed
47676         0xBA3C          Zlib compressed data, compressed
48528         0xBD90          Zlib compressed data, compressed
49268         0xC074          Zlib compressed data, compressed
50008         0xC358          Zlib compressed data, compressed
50860         0xC6AC          Zlib compressed data, compressed
51600         0xC990          Zlib compressed data, compressed
52340         0xCC74          Zlib compressed data, compressed
53192         0xCFC8          Zlib compressed data, compressed
53932         0xD2AC          Zlib compressed data, compressed
54672         0xD590          Zlib compressed data, compressed
55524         0xD8E4          Zlib compressed data, compressed
56196         0xDB84          JFFS2 filesystem, big endian

This is crazy! There are several JFFS2 filesystems, along with quite a few Zlib compressed sections of data. I wonder what shows up with strings.

The output of this is really long so I'm only including some snippets here.

strings jffs2_cfg.img
Vssh
Xssh_host_rsa_v2_key
ssh_host_rsa_v2_key.pub
ssh_host_dsa_v2_key
ssl_key.pem
ssl_cert.pem
http_rsa_key
startup-config
backup-config
tmp-startup-config
ssh_host_dsa_v2_key
ssh_host_rsa_v2_key.pub

From here, it goes on and on with more SSH keys and more references to startup-config. I am sort of interested in knowing why there are so many SSH keys, but am also pumped that startup-config is showing up!


Now, how do I actually get to the data? There are two approaches that I want to explore.

  1. Extract the filesystems onto my local workstation filesystem.
  2. Mount the image as its own filesystem.

Realistically, either of these options should have the same effect: the JFFS2 image will show up as files on my computer. They can then be browsed using either a terminal or file manager like any other files on my computer.

Extracting Files

binwalk can be used to extract the files in the same way that we did with the SquashFS image in the first article, Exploring Firmware of a Zyxel Switch. Doing so requires a second program called Jefferson.

Mounting Image

We can mount the JFFS2 image just like any other image or drive. This will require a few kernel modules to be installed.


Extraction With Binwalk and Jefferson


Extracting the content of the image is pretty straightforward. We are able to use binwalk with the extract and Matryoshka flags. But there is a catch - binwalk needs to use a second program called Jefferson to do the extraction.

How to install Jefferson will vary depending on what system you are on. I am using an Arch workstation and was able to find it in the AUR. I was not, however, able to build the version in the AUR. I ended up going against Arch guidelines and installing it with PIP. Maybe by the time you are reading this the AUR version will build again, or you are on a different system that is a bit simpler.

With Jefferson installed, we can just do:

binwalk -eM jffs2_cfg.img
ls _jffs2_cfg.img.extracted
0.jffs2    1B04        5174.jffs2  6B18        6F8C.jffs2  940.zlib    jffs2-root-0  jffs2-root-3  jffs2-root-6
1A48       1B04.zlib   649C.jffs2  6B18.zlib   7298.jffs2  D074.jffs2  jffs2-root-1  jffs2-root-4  jffs2-root-7
1A48.zlib  4F10.jffs2  688C.jffs2  6B90.jffs2  940         jffs2-root  jffs2-root-2  jffs2-root-5

This ends up extracting several JFFS2 filesystems from the single image. There are no less than 6 filesystems that are extracted and ready to be viewed. Exciting!

Now to just take a look and see what we have.

ls _jffs2_cfg.img.extracted/jffs2-root -Al
backup-config  ssh  tmp-startup-config

ls _jffs2_cfg.img.extracted/jffs2-root-0 -Al
backup-config  ssh  tmp-startup-config

ls _jffs2_cfg.img.extracted/jffs2-root-1 -Al
backup-config  ssh  tmp-startup-config

ls _jffs2_cfg.img.extracted/jffs2-root-2 -Al
backup-config  tmp-startup-config

ls _jffs2_cfg.img.extracted/jffs2-root-3 -Al
tmp-startup-config
...

Why are there so many duplicates?

This is part of how JFFS2, or Journalling Flash Filesystem Version 2, works. When you create a file, a new journal node is created that describes the location of this file and the name. If you then delete the file, another new node is created that indicates that the file has been deleted. The original file creation node still exists. This can be marked as obsolete later and removed, but it isn't necessarily going to happen instantly.

When a JFFS2 partition is mounted the full journal needs to be read. The filesystem is recreated based on the entries in the journal. What you would see on the target system is the end result of any additions, subtractions, and changes to the files that were originally there.

Even though we see multiple instances of the ssh directory, tmp-startup-config, and backup-config, these are most likely just changes that have happened to those objects over time. Suspiciously, startup-config is missing completely.

This has definitely moved me closer to the goal, but it seems like I might be on a witch hunt.


Mounting a JFFS2 Filesystem


It is actually rather hard to find any information about how to do this that is less than 10 years old. And it seems like the old information is a bit wrong.

At least three file systems have been developed as JFFS2 replacements: LogFS, UBIFS, and YAFFS.

JFFS2 was introduced in 2001 and successors were created not long after that. Most of the information that I could find online seems to be within that time range.


We need to make sure a few kernel modules are present.

  • mtdram
  • mtdblock
  • jffs2

Most articles online also reference mtdchar. This was needed before kernel v3.9 but has since been merged into mtdcore.

When we initialize the mtdram kernel module we need to pass the total size of the image in kilobytes. In this case it is super easy because it is exactly 1024 KB. However, to calculate what to use on other images:

du -sk jffs2_cfg.img

We can then use the result of that while initializing the image.

modprobe mtdram total_size=[IMAGE_SIZE_IN_KB] erase_size=128
modprobe mtdblock
mkdir mnt
dd if=jffs2_cfg.img of=/dev/mtdblock0
mount -t jffs2 /dev/mtdblock0 mnt

mount: /home/vivek/Projects/zyxel/docs/mnt: can't read superblock on /dev/mtdblock0.
       dmesg(1) may have more information after failed mount system call.

 

Come on. CaN't ReAd SuPeRbLoCk.

dmesg | tail -n 200

[604946.026542] jffs2: version 2.2. (NAND) (SUMMARY)  © 2001-2006 Red Hat, Inc.
[604946.026844] jffs2: Magic bitmask is backwards at offset 0x00000000. Wrong endian filesystem?
[604946.026848] jffs2: jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x00000008: 0x60f0 instead
[604946.026856] jffs2: Further such events for this erase block will not be printed
[604946.026870] jffs2: Old JFFS2 bitmask found at 0x00001614
[604946.026871] jffs2: You cannot use older JFFS2 filesystems with newer kernels
[604946.027005] jffs2: Empty flash at 0x0000e140 ends at 0x00010000

At least this is helpful. I'm using an x86 workstation, which is little-endian. The MIPS processor in this switch is big-endian. (Endianness) Maybe this kernel driver will only work with the same endianness of the host?

This image is also apparently an older JFFS2 filesystem. I was not aware that older JFFS2 filesystems were not able to be used with newer kernels.

Luckily, the mtd-utils package comes with a tool called jffs2dump. We are able to pretty easily convert the big endian image to little endian.

jffs2dump -r -e jffs2_le.img -b jffs2_cfg.img

This threw a bunch of warnings but did create the new image. Let's see if it can be mounted.

sudo dd if=jffs2_le.img of=/dev/mtdblock0
[sudo] password for vivek: 
1951+1 records in
1951+1 records out
999180 bytes (999 kB, 976 KiB) copied, 0.0037887 s, 264 MB/s

sudo mount -t jffs2 /dev/mtdblock0 mnt

Well... it didn't throw any errors!

ls mnt -Al

total 3
-rw-r--r-- 1 root root 1293 Dec 31  2021 backup-config
drwxr-xr-x 2 root root    0 Dec 31  2021 ssh
-rw-r--r-- 1 root root 1293 Dec 31  2021 tmp-startup-config

The Configs


Alright, so this has been an epic journey so far. I am hunting for a file called startup-config but it does not want to be found. startup-config's brother and sister are around, but he just noped out. Dude just skipped town and went into hiding.

What can tmp-startup-config tell us?

cat mnt/tmp-startup-config
! System Description: ZyXEL GS1900-24HP Switch
! Revision: B1
! Serial Number: S222L31002671
! MAC Address Range: D8:EC:E5:BB:5E:08 - D8:EC:E5:BB:5E:22
! Boot Version: V2.0.2.5 | 08/31/2021
! Firmware Version: V2.80(ABTP.0) | 01/11/2022
! System Up Time: 0 days, 0 hours, 1 mins, 52 secs
!
!
!
ip address 192.168.1.1 mask 255.255.255.0
username "admin" secret 8 $8$hytCmvXU$dfcb8b69b124f7b794afd232bfdf2d0aa1d92a7c6229b52f7401f177a1201487
voice-vlan oui-table 00:E0:BB "3COM"
voice-vlan oui-table 00:03:6B "Cisco"
voice-vlan oui-table 00:E0:75 "Veritel"
voice-vlan oui-table 00:D0:1E "Pingtel"
voice-vlan oui-table 00:01:E3 "Siemens"
voice-vlan oui-table 00:60:B9 "NEC/Philips"
voice-vlan oui-table 00:0F:E2 "H3C"
voice-vlan oui-table 00:09:6E "Avaya"
!
!
!
!
!
spanning-tree mst configuration
 name "D8:EC:E5:BB:5E:08"
!
!
!
snmp community "public"  rw
!
!
no ip ssh
!
!
management access-list default
!
interface 1
!
interface 2
!
interface 3
!
interface 4
!
interface 5
!
interface 6
!
interface 7
!
interface 8
!
interface 9
!
interface 10
!
interface 11
!
interface 12
!
interface 13
!
interface 14
!
interface 15
!
interface 16
!
interface 17
!
interface 18
!
interface 19
!
interface 20
!
interface 21
!
interface 22
!
interface 23
!
interface 24
!
interface 25
!
interface 26
!
!
!
!

Sigh

This is just a switch config. This is not a script that configures the operating system. How the files from the SquashFS image make it to the root partition is still a mystery.

I wonder if there is a way to run code directly on this switch?