Gregory Hildstrom Projects Publications Resume Contact About Youtube Donate

Hiding data in ext2 file system

The ext2 and ext3 filesystems are pretty cool, but they work so well most people probably take them for granted. One thing you can do is hide information, but I am not talking about something as simple as a hidden file like .bash_history.

Let's get the inode number of a file.
[root@terlingua /]# cd /boot/grub
[root@terlingua grub]# ls -i grub.conf
18073 grub.conf

Then let's find out the fundamental file system block size.
[root@terlingua grub]# stat -f grub.conf
  File: "grub.conf"
    ID: 0        Namelen: 255     Type: ext2/ext3
Block size: 1024       Fundamental block size: 1024
Blocks: Total: 101086     Free: 83558      Available: 78339
Inodes: Total: 26104      Free: 26068

Now we can use debugfs to get the physical data blocks that the file's inodes point to. In this case it only has one physical data block.
[root@terlingua grub]# debugfs -R "stat grub/grub.conf" /dev/sda1
debugfs 1.39 (29-May-2006)
Inode: 18073   Type: regular    Mode:  0600   Flags: 0x0   Generation: 425928246
8
User:     0   Group:     0   Size: 739
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 2
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x48f756ba -- Thu Oct 16 09:59:06 2008
atime: 0x491893f9 -- Mon Nov 10 14:05:13 2008
mtime: 0x48f756ba -- Thu Oct 16 09:59:06 2008
BLOCKS:
(0):77313
TOTAL: 1

Now that we know the physical data block number and the file system's block size, we can read the data off of the hard disk.
[root@terlingua grub]# dd skip=77313 bs=1024 count=1 if=/dev/sda1 | head -n 1
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.01572 seconds, 65.1 kB/s
# grub.conf generated by anaconda

If we save the block and pipe it to less, we can see that the file only occupies a portion of the block.
[root@terlingua grub]# dd skip=77313 bs=1024 count=1 if=/dev/sda1 of=block; cat block | less
#          initrd /initrd-version.img
#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-92.el5)
        root (hd0,0)
        kernel /vmlinuz-2.6.18-92.el5 ro root=/dev/VolGroup00/LogVol00 rhgb quie
t
        initrd /initrd-2.6.18-92.el5.img
title CentOS (2.6.26.5)
        root (hd0,0)
        kernel /vmlinuz-2.6.26.5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.26.5.img
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@
(END)

Now we can modify the block and store some data in there past the end of the file. The resulting file is smaller because it truncated the end of the file.
[root@terlingua boot]# echo "some hidden data" | dd seek=900 bs=1 of=block
17+0 records in
17+0 records out
17 bytes (17 B) copied, 0.000112 seconds, 152 kB/s
[root@terlingua boot]# ls -l block
-rw-r--r-- 1 root root 917 Feb 12 11:54 block

Now we can write the modified block back to the disk... very carefully!
[root@terlingua boot]# dd seek=77313 bs=1024 if=block of=/dev/sda1
0+1 records in
0+1 records out
917 bytes (917 B) copied, 5.4e-05 seconds, 17.0 MB/s
[root@terlingua boot]# dd skip=77313 bs=1024 count=1 if=/dev/sda1 | less
#          initrd /initrd-version.img
#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-92.el5)
        root (hd0,0)
        kernel /vmlinuz-2.6.18-92.el5 ro root=/dev/VolGroup00/LogVol00 rhgb quie
t
        initrd /initrd-2.6.18-92.el5.img
title CentOS (2.6.26.5)
        root (hd0,0)
        kernel /vmlinuz-2.6.26.5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.26.5.img
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@some hidden data
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
(END)

Now verify that normal file system based I/O does not show the data.
[root@terlingua boot]# tail grub/grub.conf
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-92.el5)
        root (hd0,0)
        kernel /vmlinuz-2.6.18-92.el5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.18-92.el5.img
title CentOS (2.6.26.5)
        root (hd0,0)
        kernel /vmlinuz-2.6.26.5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.26.5.img

The recorded file size of 739 bytes in the file's primary inode never changed, so the remaining bytes in the last block are not technically part of the file. The hidden data in this example will persist unless the file grows to over 900 bytes.