函数说明

.fini_array函数指针其实是一个数组指针,其中存有一系列函数指针,这些函数是main函数执行完后才会自动触发。同理,在.init_array中的函数是在main函数之前就触发,一般是用来初始化程序运行的条件。

我们的利用一般是覆盖.fini_array中函数指针,使main函数执行完后控制程序执行流到后门函数。

原理调试

这里有一个test程序,用来看看.fini_array原理:

#include <stdio.h>
#include <stdlib.h>

static void start(void) __attribute__ ((constructor));
static void stop(void) __attribute__ ((destructor));

int main(int argc, char *argv[])
{
printf("start == %p\n", start);
printf("stop == %p\n", stop);
return 0;
}

void
start(void)
{
printf("hello world!\n");
}

void
stop(void)
{
printf("goodbye world!\n");
}

这两行分别定义了函数属性:start函数定义为构造函数,stop函数定义为析构函数。
构造函数在main之前执行,析构函数则在main之后。

static void start(void) __attribute__ ((constructor));
static void stop(void) __attribute__ ((destructor));

看看程序的运行结果:

objdunp -h main查看一下.fini_array.init_array的地址:

进入gdb中调试查看:

可以看到,他们分别存了startstop函数指针。
举个例子:
.fini_array中存有:0x401120和0x4011b7,其中0x4011b7就是stop函数地址,0x401120是我们的利用重点,下面注重讲解。

利用分析

重写一个程序,我们不再绑定startstop函数的属性,只是将他们设置为静态函数。

#include <stdio.h>
#include <stdlib.h>

static void start(void);
static void stop(void);

int
main(int argc, char *argv[])
{
printf("start == %p\n", start);
printf("stop == %p\n", stop);

return 0;
}

void
start(void)
{
printf("hello world!\n");
}

void
stop(void)
{
printf("goodbye world!\n");
}

编译运行:

现在已经不会执行startstop函数了,我们猜测在.fini_array.init_array中已经没有这两个函数地址了。

调试看看:
objdunp -h main查看一下.fini_array.init_array的地址:

上gdb:

可以看到第二个地址并不是stop函数地址。

其实这里主要发挥作用的就是0x401120这个函数,这就是__do_global_dtors_aux在程序结束时,__do_global_dtors_aux也就是0x0000000000401120这个函数指针会被实现

利用时,我们只需要就这个地址覆盖为后门函数即可:

看到返回地址在_dl_fini+526,所以可以得出结论,.fini_array区节的第一个函数指针在程序结束时,由_dl_fini函数调用,所以我们可加以利用。在未开启PIE的情况下,只需实现一个任意地址写,将.fini_array区节的第一个函数指针改写成后门地址或者one_gadgets,在程序结束时便能控制流程

至此,函数利用完成。
感谢重写.fini_array函数指针 | Note (gitbook.io)