如何编写一个shellcode
如果我们想要从一个程序中获取系统的shell,便需要我们当前的进程创建一个全新的进程,来让我们可以与系统直接进行交互。
什么是shell?
简而言之,就是用户和linux内核之间的接口程序,用户(即我)输入的每个命令都由shell先解释后传给linux内核
而在linux中,有两种方法创建新进程:一是通过现有的进程来创建,并替换正在活动的;二是利用现有的进程来生成它自己的拷贝,并在它的位置运行这个新进程。而execve()系统调用就是在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。
系统调用在linux中一般通过指令int 0x80
来实现,而软中断以后,会在eax中读取系统调用号,调用相对应的函数,当系统调用参数小于等于6个时,参数按顺序放在ebx,ecx,edx,esi,edi,ebp中
大于6个时,全部参数应存在一块连续的内存空间里,同时在寄存器ebx中保存指向该内存区域的指针
64位shellcode
64位系统中的系统调用编号在/usr/include/x86_64-linux-gnu/asm/unistd_64.h
,我们使用以下命令查找execve的系统编号:
1 | $ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve |
可得execve的系统调用号是59即0x3b
调用execve需要传入三个参数,分别是新打开的应用或者脚本的路径、参数的字符串数组argv(最后一个元素是NULL)、环境变量的字符串数组envp(最后一个元素是NULL)execve("/bin/sh", NULL, NULL);
。
64位中当系统调用参数小于等于6个时,参数按顺序放在rdi,rsi,rdx,r10,r8,r9中
大于6个时,全部参数应存在一块连续的内存空间里,同时在寄存器ebx中保存指向该内存区域的指针
我们用汇编语言编写,测试环境是64位ubuntu16.04,这里我们读取/bin/sh字符串并加上0xff给寄存器rbx,然后右移8位去除(因为shellcode里面不能有0x00?我自己测试有0x00会无限失败),然后将rbx内容push到栈中,并把地址传给第一个参数rdi,把rdx和rsi清零,给rax赋予0x3b操作值
需要注意的是由于是小端,这里传入的是倒着的/bin/sh
1 | '/bin/sh'[::-1].encode('hex') |
1 | ;test.s |
使用汇编编译工具NASM编译,因为是在64位系统中,需要指定elf64
1 | $ sudo apt-get install nasm |
使用以下命令提取shellcode
获得字符串形式
1 | $ objdump -s test |
然后放入python文件中
1 | code = '48bbff2f62696e2f736848c1eb08534889e74831d24831c04831f6b03b0f05' |
运行即可获得shellcode
然后我们使用c语言来验证
1 |
|
1 | $ gcc -o s1 s1.c |
运行即可get shell
32位shellcode
32位下使用int 80,execve的中断号是11
1 | ;test32.s |
1 | $ nasm -f elf test32.s |
利用以下命令可获取shellcode(在64位下会失败,少读取字节,还是建议用上文中的py脚本)
1 | objdump -d ./test32|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' |
使用c代码测试
1 |
|
编译
1 | $ sudo apt-get install g++-multilib libc6-dev-i386 |
运行成功
参考: