The cuckoo's nest

Part 2 - Logging in as rootTop

First attemptsTop

In the previous part, I've mentioned that it's easy to get a shell straight after boot just by running the following commands in U-Boot:

U-Boot> setenv bootargs init=/bin/sh $(bootargs)
U-Boot> run bootcmd
SF: 1572864 bytes @ 0x40000 Read: OK
## Booting kernel from Legacy Image at 80007fc0 ...
   Image Name:   Linux-3.10.103+
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1548688 Bytes = 1.5 MiB
   Load Address: 80008000
   Entry Point:  80008000
   XIP Kernel Image ... OK

Starting kernel ...

Uncompressing Linux... done, booting the kernel.

...

VFS: Mounted root (cramfs filesystem) readonly on device 31:2.
Freeing unused kernel memory: 140K (c03dd000 - c0400000)
/bin/sh: can't access tty; job control turned off
~ # 

This allows us to browse the filesystem and look at the system state before any userspace runs. Some useful startup commands can be found in /etc/init.d/rcS

/etc/init.d/dnode

/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

mount -t squashfs /dev/mtdblock3 /usr
mount -t cramfs /dev/mtdblock3 /usr
mount -t cramfs /dev/mtdblock4 /mnt/custom
mount -t jffs2  /dev/mtdblock5 /mnt/mtd

That's useful, but getting a shell on a running userspace would be even more useful. Unfortunately, replacing init messes something up. Even if I immediately replace the shell with the real init (exec /sbin/init), the kernel Oopses during the userspace startup and reboots shortly afterwards.

Setting up a research environmentTop

I don't (yet) have an easy way to debug processes running on the camera. Instead of messing with gdbstub, I decided to run them on my PC, with qemu-user.

I've extracted the filesystem image (obtained in the previous part) using binwalk, and put together a minimal "sysroot" environment to try and run various executables. This was done by repeatedly running

LD_LIBRARY_PATH=. qemu-arm -L . ./login

and copying the missing library files into the current directory until the executable started to produce output.

As for debugging support, QEMU has a bulitin -strace option, and acepts gdb connections when run with -g PORTNUM.

I've needed to patch-out a test by which login refused to start from a non-root account. Also, QEMU doesn't (as to my knowledge) provide a chroot-like environment, so I've modified the binary, replacing strings like /etc/passwd with ./tc/passwd, and so on. Finally, I've reached a point where the login process displayed its prompt, accepted the root login and dutifully rejected the xmhdipc password.

Anti-debugging measures?Top

No, I don't really think that this was intended as an anti-debugger measure, but for some reason the login binary closes all "unexpected" file descriptors, including QEMU's gdbserver connection. Yet another call to patch-out.

What happens in login?Top

First, it turns out that the login binary uses a different passwd file, by means of a mount-bind:

17451 mount("/mnt/mtd/Log/ns","./tc/passwd",NULL,MS_BIND,NULL) = -1 errno=1 (Operation not permitted)
17451 open("./tc/passwd",O_RDONLY) = 4
17451 ioctl(4,21505,-70208,0,-70152,-70208) = -1 errno=25 (Inappropriate ioctl for device)
17451 read(4,0x670d0,4096) = 59
17451 close(4) = 0
17451 umount2("./tc/passwd",0) = -1 errno=1 (Operation not permitted)

 It actually sounds reasonable, because the root filesystem image is read-only. Having the password file on a read-write filesystem allows it to be changed, perhaps even randomly generated for each device. I would have used a symlink instead of messing with /sbin/login though :)

A quick look on the camera boot-shell confirmed that there is a password file at /mnt/mtd/Log/ns, with the following contents. It would be interesting to check if the contents change after a factory-reset:

root:697SJ23Ru0j3M:0:0:root:/:/bin/sh

Yes, this is an old-style DES-encrypted password :) Unfortunately, I haven't managed to crack it. Instead, I've replaced the 'ns' file with the contents of /etc/passwd (with a known password hash).

Reboot, retry, no, it still doesn't work. I really should have expected this, since during the QEMU-based tests, the mount-bind trick failed, and login used the original password file and still refused entry.

Digging in deeperTop

The conventional way to check UNIX passwords is the crypt function. I've set up a breakpoint at crypt and attempted to log in as 'root' with the password 'a'. This was the result:

Breakpoint 2, 0xff7c1490 in crypt () from ./libcrypt.so.0
1: x/i $pc
=> 0xff7c1490 <crypt>:  ldr r2, [pc, #136]  ; 0xff7c1520 <crypt+144>
(gdb) p (char*)$r0
$45 = 0xfffeedb0 "Jszew7cB"

Ok, what is Jszew7cB ? Apparently the firmware authors decided that they need to preprocess the passwords before validating them. This is how their method works:

  1. Compute an MD5 hash of the password. Because my MD5 is better than the system's MD5 :)
  2. Byte-reverse each 32-bit part of the hash
  3. Byte-reverse each 32-bit part of the hash AGAIN, but with a different implementation. Yes, this effectively undoes step 2.
  4. For each successive pair of bytes, compute their sum, modulo 62. The result is mapped to characters '0'..'9', 'A'..'Z', 'a'..'z'

I don't have any idea what's the purpose of this exercise. But this explains why it's difficult to crack the default password with the usual password cracking tools. Yes, it would be dead simple to add this preprocessing step to a password cracker, but I didn't bother.

UPDATE the same hashing method has been described here as Dahua hash.

Getting in, finallyTop

At this point, I've decided to stop investigating and get to the point. I know that the password 'a' gets translated to 'Jszew7cB'. So, I simply set the root password to 'Jszew7cB'.

~ # /bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
~ # mdev -s
~ # mount -t cramfs /dev/mtdblock3 /usr
~ # mount -t cramfs /dev/mtdblock4 /mnt/custom   
~ # mount -t jffs2  /dev/mtdblock5 /mnt/mtd
~ # mount --bind /mnt/mtd/Log/ns /etc/passwd
~ # echo root:Jszew7cB | busybox chpasswd
Password for 'root' changed
~ # reboot -f

... and finally ..

(none) login: root
Password: a
Jan 12 23:04:59 login[643]: root login on 'ttyAMA0'
~ # 

Other possibilitiesTop

The obvious alternative approach would be to simply modify the cramfs/squashfs filesystems in order to start a telnet server during boot or something similiar.