Shell Code 原理深入剖析

來源:互聯網
上載者:User

這兩天都一直在解析3D模型資料。今天閑暇之餘寫了段測試代碼。分析下駭客們用的緩衝區溢位攻擊原理及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 [b]
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 [b]
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....空間內。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.