最近,在学习BCTF和0CTF的writeup时,注意到了一种通过DT_DEBUG
来获得库的基址的方式:BCTF里的pattern用这一方法来获得ld-linux.so的地址,0CTF里的sandbox用这一方法来获得sandbox.so的基址。之前面对ASLR,我只知道可以通过GOT来获取libc.so的地址,而其他库的地址还不清楚应该怎样取得。于是,我稍微研究了下,在此记录。
首先,通过readelf -d
,可以得到.dynamic
的信息。而有些二进制文件里的.dynamic
里包含DT_DEBUG
:
Dynamic section at offset 0x7c8 contains 20 entries:
Tag Type Name/Value
...
0x0000000000000015 (DEBUG) 0x0
...
这里DT_DEBUG
的值是0。在实际运行时,DT_DEBUG
的值是指向struct r_debug
的指针。其定义如下:
可以看到,其第二个元素是指向struct link_map
的指针。其定义如下:
于是,遍历link_map,对比l_name
,找到目标之后,就可以通过l_addr
获得那个库的基址。
实例如下,比如说我们要找ld-linux.so的基址。首先,检查.dynamic
的内容:
DT_DEBUG
是0x15,所以0x600848
那里就是DT_DEBUG
的条目,其值是0x00007f38cd4bd140
,即struct r_debug
的地址。
对照定义,我们知道第二个元素,0x00007f38cd4bd168
是link_map链表的第一个元素的地址。
而这里第二个元素l_name
是空,不是我们要找的库。于是通过第四个元素l_next
,即0x00007f38cd4bd700
,来看下一个
同理,这里也不是。继续看下一个0x00007f38cd4ba658
这里是libc.so,那么我们继续看下一个0x00007f38cd4bc998
OK,这里就是ld-linux.so了。l_addr
的值是0x00007f38cd29c000
,我这里开了ASLR,而ld-linux.so的基址就正好是0x00007f38cd29c000
由此,我们得到了ld-linux.so的地址。同样的道理,我们通过遍历link_map,就可以得到所有库的地址。当然,前提是二进制文件需要有DT_DEBUG
。
Edit: 最近研究才意识到,不一定需要从DT_DEBUG
去获得link_map
的地址。事实上,.got.plt
的前3项,分别是.dynamic
的地址,link_map
的地址和_dl_runtime_resolve
的地址。在解析函数地址时,link_map
会被作为参数推到栈上传递给_dl_runtime_resolve
。关于这一过程及利用方式,详情可见http://rk700.github.io/article/2015/08/09/return-to-dl-resolve