shellcode编写
shellcode可以用于在栈溢出或者堆溢出的漏洞中用于跳转后执行的代码。本文的代码是在ubuntu 18.04 64位的linux操作系统。
首先是一个用于执行shellcode的c语言程序:
1 | char shellcode[] = ""; // 之后存放shellcode进来 |
其中(*(void(*)()) shellcode)();
拆开来看首先是(void(*)()) shell code
,将shellcode转换为函数指针,指向void()形式的函数,再加一个*
是对这个指针进行取值,然后再最右边使用()
调用这个函数。在调用后会执行shellcode中的代码。
为了实现生成新的shell进程,这里使用execve系统调用,execve需要传入三个参数,分别是新打开的应用或者脚本的路径、参数的字符串数组argv(最后一个元素是NULL)、环境变量的字符串数组envp(最后一个元素是NULL)execve("/bin/sh", NULL, NULL);
。
使用syscall
汇编指令调用系统调用,需要查看系统调用号64位的系统在/usr/include/x86_64-linux-gnu/asm/unistd_64.h
文件夹里。使用下面的命令查看:
1 | cat unistd_64.h | grep execve |
可以看到execve的系统调用号是59,十六进制的0x3b。
然后编写shellcode汇编代码,核心是syscall
汇编指令,这个指令中rax寄存器存放系统调用编号,这里是0x3b,在x86-64里,使用rdi、rsi、rdx寄存器分别存放第一、第二、第三个参数,一共可以用6个寄存器存放参数,多出的参数或者参数不是数字都是使用栈来传送。rdi寄存器存放execve的第一个参数,rsi存放参数字符数组地址(这里使用NULL,也就是0),rdx存放环境变量数组地址(也是NULL)。下面是汇编代码:
1 | ; test.s |
对于第二、第三参数,为了让代码变得比较短,不使用mov rsi, 0
来实现寄存器变为0,而是使用xor rsi, rsi
来清零rsi。
传递路径给系统调用,如上面代码所示,使用栈存放路径,最后将栈地址存放到rdi中,对于这种方法需要将/bin/sh
转换为十六进制小端顺序,然后在末尾补上0,因为nasm不能一次push进4字的数据,但是它每次push会是rsp减少8,所以这里是分两次存入的,栈是从高到低的,首先放进0x6e69622f
,然后存入0x0068732f
,最后将栈指针给rdi(第一参数)。
编写好后使用汇编编译工具NASM编译:
1 | nasm -f elf64 test.s |
因为是在64位系统中,需要指定elf64,然后使用链接器将其变为可执行程序。
1 | ld test.o -o test |
最后执行test可以看到一个新的sh。
既然汇编代码已经写好,就需要将其变为字符串形式,使用:
1 | objdump -s test |
得到.text代码,将其放入python脚本中
1 | # shellcode.py |
将输出复制到shellcode字符数组中,我们看看可不可以执行shellcode了
1 | // shellcode.c |
因为GCC默认是有exec保护,所以编译时需要增加编译选项:
1 | gcc -z execstack shellcode.c |
最后可以看到效果,打开了一个新的sh:
1 | ss@ss-VirtualBox:~/Downloads$ ./a.out |
附录
可以使用gdb调试shellcode.c,编译后的函数指针指行是一个call *地址的指令,可以step in来查看行之后的shellcode里面的代码。
因为这里用了execve这个系统调用,我们可以使用strace命令,查看程序的系统调用执行状况,前几次都出现execve(null,null,null)这样参数传递错误的情况,我才想起来64位参数传递用的是rsi、rdi这些寄存器。
使用label: db ‘/bin/sh’,mov rdi, label的形式会出现找不到路径,因为ld后的代码引用label的地址竟然是固定的,我得再看看链接的原理。
edb debugger好好用!
参考
- 本文链接:https://dowob.cn/2019/07/26/shellcode%E7%BC%96%E5%86%99/
- 版权声明:本站所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!