飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 3557|回复: 3

Shell Code 原理深入剖析

[复制链接]
  • TA的每日心情
    慵懒
    2019-3-12 17:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2010-9-7 17:51:44 | 显示全部楼层 |阅读模式
    本帖最后由 whypro 于 2010-9-7 17:57 编辑

    分析下黑客们用的缓冲区溢出攻击原理及Shell code原理。好,直接进入正题。有什么说得不对的地方还望大家纠正。嘿嘿!
    首先来这么一段小小的测试代码:
    void test( void )
    {
        cout << "Success!" << endl;
    }
    int main( void )
    {
        int a[ 1 ];
        a[ 3 ] = ( int )test;
        return 0;
    }
    上面这段代码,可以简单的解释缓冲区溢出的原理,首先定义了一个整形数组a,红色部分代码已经写入越界。导致的结果就是会输出:Success!
    这里就有一个疑问了,为什么在程序里没有调用test函数,也执行了test函数里面的代码呢?
    这里就是缓冲区溢出导致的结果。这里要从函数的调用原理来解释这种现象,在函数被调用时,会保存函数的栈帧。会将ebp, eip进行压栈保存。顺序就是:
    高地址 低地址
    [ eip ] [ ebp ] [ a[ 0 ] ]
    在汇编层面,调用函数会call [ 地址 ],call又两步骤,一是push eip到堆栈进行保存作为函数的ret返回地址,eip的值就是当前call指令的下一条指令的地址。当函数结束时,执行ret指令就会跳转到eip所存的地址,也就是主调函数的栈帧里面,完成调用。
    说了这么多,这里的a[ 3 ]将test的地址当好覆盖到存放eip的地址上了。main函数的ret指令便跳转到test的代码空间里了。所以便输出了Success! 当然这样程序会崩溃,因为执行到test的ret指令时,此时的eip的值已经未知了,堆栈已经不平衡。因此跳转将到未知地方,便崩溃了。
    从上面的例子中不难看出,我们可以通过Buffer Overflow来改变在堆栈中存放的函数返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方。这就为黑客们提供了可乘之机。
    最常见的方法是:在长字符串中嵌入一段代码(就是通过溢出越界写入,覆盖掉函数的返回地址),并将函数的返回地址覆盖为这段代码的地址, 这样当函数返回时,程序就转而开始执行这段我们自编的代码了。 一般来说,这段代码都是执行一个Shell程序(如\bin\sh),因为这样的话,当我们入侵一个带有Buffer Overflow缺陷且具有suid-root属性的程序时。 我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事。因此,这段代码一般被称为Shell Code。
    下面我们来举个例子说明下Shell Code的原理:
    int Code( int a, int b )
    {
        return a + b;
    }
    void TestShell( void )
    {
        int result = 0;
        BYTE FuncByte[ 512 ];
        BYTE* JmpAddr = ( BYTE* ) Code;
        DWORD ofsFuncAddr = *( ( DWORD* )( JmpAddr + 1 ) ) + 5;
        BYTE* FuncAddr = ( BYTE* )( ( ( DWORD )JmpAddr ) + ofsFuncAddr );
        BYTE* pFuncBuff = FuncAddr;
        BYTE* pInput = FuncByte;
       
        while ( true )
        {
            if ( (*pInput++ = *pFuncBuff++ ) == 0xC3 )
            break;
        }

        __asm
       {
            lea eax, FuncByte
            push 100
            push 200
            mov ecx, 1
            call __label

       __label:
            cmp ecx, 0
            je __ret
            sub ecx, 1
            jmp eax
      
       __ret:
            mov result, eax
            add esp, 8
        }
       
        cout << result << endl;
        system( "pause" );
    }

    int main( void )
    {
        TestShell ();
        return 0;
    }
    上面的FuncByte用于保存Code函数的字节码,JmpAddr指向jmp到Code函数的jmp指令地址。call [目标函数] 会先跳转到jmp [ 函数地址 ]指令的地址上,然后才会jmp到目标函数的首地址上。ofsFuncAddr用于保存当前jmp指令5个字节中后4个字节保存的函数地址偏移量(这里暂不管跳转的远近,这里是无条件转移,就粗略认为是4个字节存放的是偏移)。
       指令地址            字节码          指令        目标函数地址
    0041954B   E9 10 1D 00 00   jmp   TestShell (41B260h)
    从上面的jmp指令可以看出,E9就是jmp指令的字节码,后面蓝色的4个字节就是:目标函数的地址 - jmp指令地址 - jmp指令的5个字节。也就是:0x41B260 - 0x41954B - 5 = 0x001D10。FuncAddr保存的是目标函数的首地址。之后的while就是将Code函数的字节码拷贝到FuncByte里。因为函数结束会执行ret指令,ret指令的字节码就是0xC3。所以我们以它来终止循环停止拷贝。
    之后的汇编代码是为了执行我们拷贝的字节码,并维护堆栈平衡,让跳转地址正确跳转到__ret后面的 mov result, eax 语句。但是要怎么样才能让执行了我们拷贝的字节码后正确跳转到我们想要的位置呢?这里我们使用call指令来完成这项工作,红色的call __label会将下一条汇编语句的地址压入堆栈,作为函数的返回地址。由于FuncByte里面存放的是Code函数的字节码,因此执行FuncByte里面的字节码与Code函数的效果是一样的。这里执行FuncByte直接用jmp eax来进行跳转。执行到0xC3(ret)字节码后,就会跳转到cmp ecx, 0这条语句上。这里我做了个限制使用ecx计数让ret回来后因为ecx为零(sub ecx, 1 ),执行je __ret。红色的代码段也可以用 push __ret 一条指令来替换,相当于把返回地址push到堆栈,当拷贝的字节码执行完后返回到__ret:。这里只是为了说明CALL指令的原理。 然后将返回值赋给result。之后pop掉两个参数100, 200。维持堆栈平衡。之后就是打印result的值:300。实现了ShellCode的原型。
    好了,基本上是说完了!这里的Code函数里面只是简单的一条语句,如果有复杂的操作还需要进一步处理FuncByte里面的字节码。比如,Code函数里面有函数调用,将会有jmp跳转。而jmp跳转使用的是距当前语句的指令地址的偏移量。FuncByte是一临时的字节数组,执行的字节码的指令地址也将在临时的地址空间里。字节码不变的情况下,jmp指令的地址变了,自然jmp同样的偏移是不会跳转到正确的目标函数地址的。我的初步想法是在拷贝字节码的同时对使用偏移的指令进行特殊计算处理。让在临时地址空间中也能正确跳转。暂时留个思绪,抛砖引玉!各位多多指教! - -
    如果Code函数里面有函数调用:
    int Code( int a, int b )
    {
        cout << a + b << endl;
        return a + b;
    }
    下面是我们拷贝的字节码和Code函数的字节码对比:

    FuncByte的拷贝字节码:
    0013FBF8    55                                push ebp
    0013FBF9    8B EC                          mov ebp,esp
    0013FBFB    81 EC C0 00 00 00      sub esp,0C0h
    0013FC01   53                               push ebx
    0013FC02   56                               push esi
    0013FC03   57                               push edi
    0013FC04   8D BD 40 FF FF FF       lea edi,[ebp-0C0h]
    0013FC0A   B9 30 00 00 00           mov ecx,30h
    0013FC0F   B8 CC CC CC CC         mov eax,0CCCCCCCCh
    0013FC14   F3 AB                          rep stos dword ptr [edi]
    0013FC16   68 D8 94 41 00           push 4194D8h
    0013FC1B   8B 45 08                     mov eax,dword ptr [ebp+8]
    0013FC1E   03 45 0C                    add eax,dword ptr [ebp+0Ch]
    0013FC21   50                               push eax
    0013FC22   B9 88 86 45 00           mov ecx,458688h
    0013FC27   E8 2B E2 FF FF            call 0013DE57
    0013FC2C   8B C8                          mov ecx,eax
    0013FC2E   E8 10 E7 FF FF             call 0013E343
    0013FC33   8B 45 08                     mov eax,dword ptr [ebp+8]
    0013FC36   03 45 0C                     add eax,dword ptr [ebp+0Ch]
    0013FC39   5F                                pop edi
    0013FC3A   5E                                pop esi
    0013FC3B   5B                                pop ebx
    0013FC3C   81 C4 C0 00 00 00      add esp,0C0h
    0013FC42   3B EC                           cmp ebp,esp
    0013FC44   E8 D0 E8 FF FF             call 0013E519
    0013FC49   8B E5                           mov esp,ebp
    0013FC4B   5D                                pop ebp
    0013FC4C   C3                                ret
    Code函数本身字节码:
    0041B770   55                                push ebp
    0041B771   8B EC                           mov ebp,esp
    0041B773   81 EC C0 00 00 00       sub esp,0C0h
    0041B779   53                                 push ebx
    0041B77A   56                                 push esi
    0041B77B   57                                 push edi
    0041B77C   8D BD 40 FF FF FF         lea edi,[ebp-0C0h]
    0041B782   B9 30 00 00 00             mov ecx,30h
    0041B787   B8 CC CC CC CC           mov eax,0CCCCCCCCh
    0041B78C   F3 AB                            rep stos dword ptr [edi]
    0041B78E   68 D8 94 41 00             push offset std::endl (4194D8h)
    0041B793   8B 45 08                       mov eax,dword ptr [a]
    0041B796   03 45 0C                       add eax,dword ptr
    0041B799   50                                  push eax
    0041B79A   B9 88 86 45 00              mov ecx,offset std::cout (458688h)
    0041B79F   E8 56 DE FF FF               call operator<< (4195FAh)
    0041B7A4   8B C8                             mov ecx,eax
    0041B7A6   E8 40 E3 FF FF               call operator<< (419AEBh)
    0041B7AB   8B 45 08                        mov eax,dword ptr [a]
    0041B7AE   03 45 0C                        add eax,dword ptr
    0041B7B1   5F                                  pop edi
    0041B7B2   5E                                  pop esi
    0041B7B3   5B                                  pop ebx
    0041B7B4   81 C4 C0 00 00 00         add esp,0C0h
    0041B7BA   3B EC                            cmp ebp,esp
    0041B7BC   E8 00 E5 FF FF              call (__RTC_CheckEsp) (419CC1h)
    0041B7C1   8B E5                            mov esp,ebp
    0041B7C3   5D                                 pop ebp
    0041B7C4   C3                                 ret
    从红色的3个call可以看出,我们的字节码没有变,也就是同样的偏移值。计算出来的call地址是不一样的。拷贝的在0x0013....空间内。而正确的应该是0x0041....空间内。
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2010-9-7 19:22:52 | 显示全部楼层
    只能是看看!
    PYG19周年生日快乐!
  • TA的每日心情
    擦汗
    2019-3-1 23:51
  • 签到天数: 559 天

    [LV.9]以坛为家II

    发表于 2015-5-25 17:41:59 | 显示全部楼层
    这个很高深,收藏一个,以后研究。
    PYG19周年生日快乐!
  • TA的每日心情
    郁闷
    2015-8-28 14:47
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2015-8-28 17:12:42 | 显示全部楼层
    谢谢楼主分享!!
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表