关于盏溢出和 Security_cookie

转自: http://blog.ixpub.net/html/19/13151619-119590.html

更新标题.

------------------------------------------------------------ 

 

春节的假期里阅读了一些漏洞的分析, 利用(exploit), Shellcode 方面的资料, 也在零散的时间里写了一些demo 的代码. 总算还了心愿; 现在对其中一些知识的感悟总结一下.
实在是不知道用什么样的题目合适, 内容是原理概要和发展展望.
1. 栈溢出的漏洞和利用原理
栈这样一个分配在 .data .text .bss 之外的地址段中的存储结构,程序中的局部变量( 包括参数) ,除了有些是使用寄存器之外,都是在栈中临时存放的,并且是由编译器在编译时决定的。
在栈中存放的数据有一些比较特殊, 其中有直接控制程序执行的代码地址:
1) 函数调用完成时的返回地址,RET 指令引用
2) 被调用的函数的地址,CALL 指令引用, 如 call func_address; 这个func_addess( 函数地址) 可能在栈中.
3) 程序跳转的地址,JMP 指令引用, 跳转的目标地址可以在栈中.
这些数据是控制程序执行的代码地址.
还有一些数据虽然不是直接的代码的地址, 但却在程序中起到控制程序流程和功能的作用. 这里面最重要的就是stack 中的Exception Handlers 结构入口
如查看如下c++ 代码的编译结果:
int main(int argc, char* argv[])
{
void *p;
try {
p=new char[100];
}catch(...){
printf("/n In exception framehandler");
}
//delete []p;
return 0;
}
vc6 release 编译后, 用ida 看汇编代码:
.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 var_10 = dword ptr -10h
.text:00401000 var_C = dword ptr -0Ch
.text:00401000 var_4 = dword ptr -4
.text:00401000 argc = dword ptr 8
.text:00401000 argv = dword ptr 0Ch
.text:00401000 envp = dword ptr 10h
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push 0FFFFFFFFh
.text:00401005 push offset loc_406800
.text: 0040100A mov eax, large fs:0
.text:00401010 push eax
.text:00401011 mov large fs:0, esp
.text:00401018 push ecx
.text:00401019 push ebx
.text:0040101A push esi
.text:0040101B push edi
.text:0040101C mov [ebp+var_4], 0
.text:00401023 mov [ebp+var_10], esp
.text:00401026 push 64h
.text:00401028 call ??2@YAPAXI@Z ; operator new(uint)
.text:0040102D add esp, 4
.text:00401030
.text:00401030 loc_401030: ; DATA XREF: sub_401043+Do
.text:00401030 mov ecx, [ebp+var_C]
.text:00401033 pop edi
.text:00401034 pop esi
.text:00401035 xor eax, eax
.text:00401037 mov large fs:0, ecx
.text:0040103E pop ebx
.text:0040103F mov esp, ebp
.text:00401041 pop ebp
.text:00401042 retn
.text:00401042 _main endp

.text:00401043 sub_401043 proc near ; DATA XREF: .rdata:stru_407590o
.text:00401043 push offset aInExceptionFra ; "/n In exception framehandler exception"
.text:00401048 call sub_401060
.text:0040104D add esp, 4
.text:00401050 mov eax, offset loc_401030
.text:00401055 retn
.text:00401055 sub_401043 endp

.text:00406800 loc_406800: ; DATA XREF: _main+5o
.text:00406800 mov eax, offset stru_407548
.text:00406805 jmp ___CxxFrameHandler
可以看出, 编译系统将异常处理程序和关键数据结构的入口点也放在了栈中.
应用程序从网络, 磁盘, 端口等I/O 方式或者的输入的数据的长度可能超过了在stack 中分配的缓冲区的大小而应用程序并没有检查, 这样数据就会覆盖和容纳输入数据的缓冲区相邻的stack 中的代码地址, 从而改变了程序的执行的原有的流程.
通过精确地控制输入的数据的长度和内容, 能够使应用程序的执行转向非设计者预期的, 错误的, 甚至完全利用者输入的包含在数据中的恶意代码.
这里的执行转向可能有两种:
1) 原程序中的代码段.text 中的地址中的代码.
2) 利用者输入的包含在数据中, 并被程序拷贝在stack 中的代码.
2. heap 溢出的漏洞和利用原理
Heap(堆)被应用程序用来管理应用程序的动态内存分配,如一般情况下,使用new, malloc等函数分配的内存就是使用堆(heap)数据结构。
涉及堆溢出的情况有如下几种:
首先看这个例子:

#include <stdlib.h>
#include <string.h>
typedef int (tfunc )(void);
struct node{
char buf[128];
tfunc *myfuncp;
};
int afunc(void);
int main(int argc, char* argv[])
{
struct node *pnode;
pnode=new struct node;
pnode->myfuncp=&afunc;
sprintf(pnode->buf,"Hello World! %s",argv[1]);
(*(pnode->myfuncp))();
delete pnode;
return 0;
}
int afunc()
{
printf("/n in afunc");
return 0;
}

VC6 release 编译之后的,用IDA 察看汇编代码:
.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 argc = dword ptr 8
.text:00401000 argv = dword ptr 0Ch
.text:00401000 envp = dword ptr 10h
.text:00401000
.text:00401000 push esi
.text:00401001 push 84h
.text:00401006 call ??2@YAPAXI@Z ; operator new(uint)
.text:0040100B mov esi, eax
.text:0040100D mov eax, [esp+4+argv]
.text:00401011 add esp, 4
.text:00401014 mov dword ptr [esi+80h], offset loc_401040
.text:0040101E mov ecx, [eax+4]
.text:00401021 push ecx
.text:00401022 push offset aHelloWorldS ; "Hello World! %s"
.text:00401027 push esi ; char *
.text:00401028 call _sprintf
.text:0040102D call dword ptr [esi+80h]
.text:00401033 push esi
.text:00401034 call sub_401050
.text:00401039 add esp, 10h
.text:0040103C xor eax, eax
.text:0040103E pop esi
.text:0040103F retn
.text:0040103F _main endp
这段程序中,结构struct node{} 是在堆中分配的,重要的是struct node结构中包含有函数指针,并且该指针是在buf[128] 之后,开始地址放在esi 寄存器中,程序中并没有对argv[1]的长度进行限制,因此,通过设计argv[1]的长度和内容可以覆盖myfuncp,并让myfuncp指向包含在buf[128]中的代码。

暂不论128是否够长,这样就可以在不破坏heap管理结构的情况下,溢出堆中的数据结构并执行任意代码再看如下的C++代码例子:
#include <string.h>
class tcls{
private:
char buf[128];
public:
virtual int func(const char *src);
public:
tcls();
virtual ~tcls();
};
int main(int argc, char* argv[])
{
tcls *obp;
obp=new tcls;
sprintf((char *)obp,"%s",argv[1]);
obp->func("!!!");
delete obp;
return 0;
}
tcls::tcls()

{
memset(buf,0,sizeof(buf));
printf("tcls construct");
}
tcls::~tcls()
{
printf("tcls destory");
}
int tcls::func(const char *src)
{
printf(src);
return 0;
}
再看一个vc6 release 编译以后的代码:
.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 var_14 = dword ptr -14h
.text:00401000 var_10 = dword ptr -10h
.text:00401000 var_4 = dword ptr -4
.text:00401000 arg_4 = dword ptr 8
.text:00401000
.text:00401000 push 0FFFFFFFFh //
.text:00401002 push offset loc_4068DB //
.text:00401007 mov eax, large fs:0 //frame. handler
.text:0040100D push eax
.text:0040100E mov large fs:0, esp
.text:00401015 push ecx
.text:00401016 push esi
.text:00401017 push 84h //对象大小132
.text:0040101C call ??2@YAPAXI@Z ; operator new(uint)
.text:00401021 add esp, 4
.text:00401024 mov [esp+14h+var_10], eax //对象指针
.text:00401028 test eax, eax
.text:0040102A mov [esp+14h+var_4], 0
.text:00401032 jz short loc_40103F
.text:00401034 mov ecx, eax
.text:00401036 call sub_401080 //调用构造函数
.text:0040103B mov esi, eax
.text:0040103D jmp short loc_401041
.text:0040103F
.text:0040103F loc_40103F: ; CODE XREF: _main+32j
.text:0040103F xor esi, esi
.text:00401041
.text:00401041 loc_401041: ; CODE XREF: _main+3Dj
.text:00401041 mov ecx, [esp+14h+arg_4]
.text:00401045 mov eax, [esi] //this 指针
.text:00401047 mov [esp+14h+var_4], 0FFFFFFFFh
.text:0040104F mov edx, [ecx+4]
.text:00401052 mov ecx, esi
.text:00401054 push edx
.text:00401055 call dword ptr [eax] //调用obp->func("!!!");
.text:00401057 test esi, esi
.text:00401059 jz short loc_401064
.text:0040105B mov eax, [esi]
.text:0040105D push 1
.text:0040105F mov ecx, esi
.text:00401061 call dword ptr [eax+4] //调用析构函数
.text:00401064
.text:00401064 loc_401064: ; CODE XREF: _main+59j
.text:00401064 mov ecx, [esp+1Ch+var_14]
.text:00401068 xor eax, eax
.text:0040106A pop esi
.text:0040106B mov large fs:0, ecx
.text:00401072 add esp, 10h
.text:00401075 retn
.text:00401075 _main endp ; sp = -8
从代码可以看得出来,new 分配的内存块的大小为132 字节,为指向vtable 的指针4bytes 加上成员buf [128] 的大小,即this 指针指向的堆中分配的内存块首先是指向vtable 的指针4bytes, 然后是buf[128], 在这个程序当中,sprintf(); 函数首先覆盖的是指向vtable 的指针;当然这个程序是故意制造的错误。在这个程序当中,vtable 结构是指向两个函数的指针,首先是指向func() 虚函数的指针,然后是指向析构函数~tcls() 的指针。

在这种问题当中,完全可以argv[1] 中构造一串代码,并构造一个vtable ,覆盖vtable 指针并指向自己构造的vtable. 从而使func() 或者~tcls() 被调用的时候而执行自己构造的任意代码。
3. NX 和DEP 等新技术的使用对溢出攻击的影响
AMD 在Athlon 64/Athlon 64 FX/Athlon 64 移动版本/Sempron 移动版等处理器中提供了EVP 增强病毒保护功能, Intel P4 Prescott(mPGA478 与LGA775 封装) 的CPU 则提供了EDB (Execute Disable Bit ),这些CPU 的功能可以统称为NX 技术。就是说CPU 在执行包含在标记为数据页面里面的代码时会抛出异常。
而Windows xp with sp2 和Windows 2003 都提供了DEP 功能,DEP 能够利用CPU 的硬件的NX 技术,在CPU 不支持NX 的情况下,DEP 能够利用软件对Stack 和Heap 的溢出进行保护,当然需要应用软件使用C++ .NET /GS 选项进行编译。
看如下的一段小程序在C++ .NET2003 /GS 编译之后的结果
int _tmain(int argc, _TCHAR* argv[])

{
char ch[100];
char *p1,*p2;
p1= new char[16];
p2= new char[16];
printf("%s", p1, ch, p2);
return 0;
}

/GS Release 编译用IDA 看生成的汇编代码:
.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401000 _main proc near ; CODE XREF: start+16Ep
.text:00401000
.text:00401000 var_68 = dword ptr -68h
.text:00401000 var_4 = dword ptr -4
.text:00401000 argc = dword ptr 4
.text:00401000 argv = dword ptr 8
.text:00401000 envp = dword ptr 0Ch
.text:00401000
.text:00401000 sub esp, 68h
.text:00401003 mov eax, dword_409074
.text:00401008 push esi
.text:00401009 mov [esp+6Ch+var_4], eax
.text:0040100D push 10h
.text:0040100F call sub_401044
.text:00401014 push 10h
.text:00401016 mov esi, eax
.text:00401018 call sub_401044
.text:0040101D push eax
.text:0040101E lea eax, [esp+78h+var_68]
.text:00401022 push eax
.text:00401023 push esi
.text:00401024 push offset aS ; "%s"
.text:00401029 call _printf
.text:0040102E mov ecx, [esp+84h+var_4]
.text:00401035 add esp, 18h
.text:00401038 xor eax, eax
.text:0040103A pop esi
.text:0040103B call sub_40116F
.text:00401040 add esp, 68h
.text:00401043 retn
.text:00401043 _main endp
//----------------------------------------------------------------------------------------------------- 〉

.text:0040116F cmp ecx, dword_409074
.text:00401175 jnz short loc_401178
.text:00401177 retn
//----------------------------------------------------------------------------------------------------- 〉
.data:00409074 dword_409074 dd 0BB40E64Eh
编译器在编译时生成了一个随机数,存放在程序的.data 段中,即dword_409074 ,0BB40E64Eh

这是个默认值,在程序被启动的时候,运行库将调用运行库中的___security_init_cookie 函数来初始化这个随机数,但如果初始化失败,仍将使用这个默认值。

程序在调用一个函数时,在stack 上的返回地址之和函数的局部缓冲区之间,被放置了这个随机数,这个就是security_cookie, 如果要通过溢出函数缓冲区的方式来覆盖改写函数的返回地址,则必然要改写这个cookie, 当函数完成,返回之前,调用sub_40116F 来检查放在stack 上的cookie 有没有改变来判断是否发生了溢出, 显然这并不能防止所有的攻击的发生,因为即使没有覆盖返回地址来执行任意代码,然而它并不能判断出应用程序缓冲区中各个局部变量之间的溢出和覆盖, 这些局部变量也可能是存放着函数地址, 即使不是函数地址,如果被邻近的变量溢出覆盖,也将使程序的行为失去控制。 即使是运行系统检查到了溢出,程序能做的只是报告错误,并退出;并不能保证程序的原有行为的完整性。

如:
int rfunc();
int _tmain(int argc, _TCHAR* argv[])
{
char ch[100];
int (*func )() ;
func=rfunc;
printf("%d %s", func(),ch);
return 0;
}
int rfunc()
{
… …
}
上面的代码被编译之后,编译器喜欢把func 分配在ch 的前面(高地址),这样就有机会溢出func 而使其指向攻击者指定的函数,甚至是任意代码,而根本没有触及security_cookie 。这也是security_cookie 被bypass 的一种情况。

/GS 编译有时无效:函数有变长参数,naked 函数,首行为inline asm 的等等。

CPU 的NX 技术也仅能防止数据页面的执行,而不能防止数据页面本身被溢出和篡改。即使阻止了shellcode 在数据区的执行,代价是终止了程序的服务;换句话说,NX 技术自己并不阻止被溢出到代码段的其他地方胡乱执行,shellcode 仍然可以改变程序的执行流程。

如果系统 既有硬件的NX 技术支持,又有DEP 类似的编译器结合OS 的stack 和heap 保护,在最理想的情况下,能够阻止远程注入的任意代码的执行,但并不能阻止拒绝服务,也保证不了程序运行的完整性。

而一旦绝大多数系统都具备NX 和DEP 技术的时候,分布式拒绝服务的攻击将比较困难。

4. 脚本漏洞和利用原理

对于服务器和客户端来说,对方提交的数据可能并不是一般的数据,而是需要解释执行或者编译成伪码执行的脚本。或者处理对方提交的数据的并不是直接在CPU 上面跑的native 代码,而是脚本或者在VM 上跑的IL ;这个时候情况就复杂了。这些脚本数据包括:SQL script; Jscript,PHP, Perl 等等。



5. 其它设计逻辑错误漏洞和利用
如果程序中存在设计的逻辑错误,除了导致缓冲区溢出执行任意代码,拒绝服务,执行错误的运算之外;通过提交精心构造的数据可能导致代码泄漏敏感的数据或者执行破坏功能。而攻击者通过泄漏的敏感数据也能完全获得系统的控制权。
如:
To be continued... ...

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值