Note
Search…
重写.fini_array函数指针
大多数可执行文件是通过链接 libc 来进行编译的,因此 gcc 会将 glibc 初始化代码放入编译好的可执行文件和共享库中。 .init_array.fini_array 节(早期版本被称为 .ctors和 .dtors )中存放了指向初始化代码和终止代码的函数指针。 .init_array 函数指针会在 main() 函数调用之前触发。这就意味着,可以通过重写某个指向正确地址的指针来将控制流指向病毒或者寄生代码。 .fini_array 函数指针在 main() 函数执行完之后才被触发,在某些场景下这一点会非常有用。例如,特定的堆溢出漏(如曾经的 http://phrack.org/issues/57/9.html )会允许攻击者在任意位置写4个字节,攻击者通常会使用一个指向 shellcode 地址的函数指针来重写.fini_array 函数指针。对于大多数病毒或者恶意软件作者来说, .init_array 函数指针是最常被攻击的目标,因为它通常可以使得寄生代码在程序的其他部分执行之前就能够先运行。

0x01 test

我们看一个程序
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
static void start(void) __attribute__ ((constructor));
5
static void stop(void) __attribute__ ((destructor));
6
7
int main(int argc, char *argv[])
8
{
9
printf("start == %p\n", start);
10
printf("stop == %p\n", stop);
11
return 0;
12
}
13
14
void
15
start(void)
16
{
17
printf("hello world!\n");
18
}
19
20
void
21
stop(void)
22
{
23
printf("goodbye world!\n");
24
}
Copied!
gcc为函数提供了几种类型的属性,其中两个是我们特别感兴趣的:构造函数(constructors)和析构函数(destructors)。程序员应当使用类似下面的方式来指定这些属性:
1
static void start(void) __attribute__ ((constructor));
2
static void stop(void) __attribute__ ((destructor));
Copied!
带有"构造函数"属性的函数将在main()函数之前被执行,而声明为"析构函数"属性的函数则将在_after_ main()退出时执行。
程序运行结果如下:
现在我们试试 objdump -h ./test
1
[email protected]:~$ objdump -h test
2
3
test: file format elf64-x86-64
4
5
Sections:
6
Idx Name Size VMA LMA File off Algn
7
8
17 .init_array 00000010 0000000000600e00 0000000000600e00 00000e00 2**3
9
CONTENTS, ALLOC, LOAD, DATA
10
18 .fini_array 00000010 0000000000600e10 0000000000600e10 00000e10 2**3
11
CONTENTS, ALLOC, LOAD, DATA
Copied!
可以看到.init_array的地址为 0x600e00 , .fini_array的地址为 0x600e10
在gdb中分别对这两个地址跟踪一下
1
pwndbg> x/2xg 0x600e00
2
0x600e00: 0x0000000000400550 0x00000000004005bb
3
pwndbg> x/2xg 0x600e10
4
0x600e10: 0x0000000000400530 0x00000000004005cb
Copied!
分析一下结果,这里我只分析.fini_array,我们可以看到 0x600e10 中存了 0x0000000000400530 与 0x00000000004005cb
明显0x00000000004005cb是stop函数的函数指针,0x0000000000400530 同样也是一个函数指针 我们后面再讨论。

0x02 test2

下面我们就着重讨论一下0x0000000000400530,其实它指向一个函数__do_global_dtors_aux,这里我就直接说结果了 :
在程序结束时,__do_global_dtors_aux也就是0x0000000000400530这个函数指针会被实现
我们再看一个例子,其实就是前面的test程序函数少了属性,我把它定义成静态函数:
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
static void start(void);
5
static void stop(void);
6
7
int
8
main(int argc, char *argv[])
9
{
10
printf("start == %p\n", start);
11
printf("stop == %p\n", stop);
12
13
return 0;
14
}
15
16
void
17
start(void)
18
{
19
printf("hello world!\n");
20
}
21
22
void
23
stop(void)
24
{
25
printf("goodbye world!\n");
26
}
Copied!
同样编译和运行:
1
[email protected]:~$ gcc -o test2 test2.c
2
3
start == 0x4005bb
4
stop == 0x4005cb
Copied!
函数地址并没有变化,但是因为start/stop函数未设定析构与构造属性,所以没有在开始和结束时被调用。
我们试试 objdump -h ./test2
1
[email protected]:~$ objdump -h test2
2
3
test: file format elf64-x86-64
4
5
Sections:
6
Idx Name Size VMA LMA File off Algn
7
8
17 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3
9
CONTENTS, ALLOC, LOAD, DATA
10
18 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3
11
CONTENTS, ALLOC, LOAD, DATA
Copied!
可以看到.init_array的地址为 0x600e10 , .fini_array的地址为 0x600e18,和test程序有点偏差。
现在我用gdb跟踪一波,查看一下.fini_array
1
pwndbg> x/2xg 0x600e18
2
0x600e18: 0x0000000000400530 0x0000000000000000
Copied!
明显0x0000000000400530后面的函数指针没有被填充 是0x0000000000000000,所以程序结束后不会执行stop函数
现在我们控制程序执行流程,怎么控制呢?我把.fini_array的函数指针0x0000000000400530覆盖成stop函数的地址
1
pwndbg> set {int}0x600e18=0x4005cb
2
pwndbg> x/2xg 0x600e18
3
0x600e18: 0x00000000004005cb 0x0000000000000000
Copied!
输入c继续执行程序
1
pwndbg> c
2
Continuing.
3
start == 0x4005bb
4
stop == 0x4005cb
5
goodbye world!
6
[Inferior 1 (process 3920) exited normally]
Copied!
bingo,成功执行了stop函数,如果stop函数是一段shellcode我们就可以直接拿下shell

0x03 分析与总结

我们来关心一下,上面的stop在什么地方被调用。
栈回溯跟踪看一下
1
► 0x4005cb <stop> push rbp
2
0x4005cc <stop+1> mov rbp, rsp
3
0x4005cf <stop+4> mov edi, 0x40068a
4
0x4005d4 <stop+9> call [email protected] <0x400450>
5
6
0x4005d9 <stop+14> pop rbp
7
0x4005da <stop+15> ret
8
9
0x4005db nop dword ptr [rax + rax]
10
0x4005e0 <__libc_csu_init> push r15
11
0x4005e2 <__libc_csu_init+2> mov r15d, edi
12
0x4005e5 <__libc_csu_init+5> push r14
13
0x4005e7 <__libc_csu_init+7> mov r14, rsi
14
──────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────
15
00:0000│ rsp 0x7fffffffdcd8 —▸ 0x7ffff7dea7da (_dl_fini+474) ◂— test r14d, r14d
16
01:0008│ r8 r15 0x7fffffffdce0 —▸ 0x7ffff7ffe1c8 ◂— 0x0
17
02:0010│ 0x7fffffffdce8 —▸ 0x7ffff7ffe760 —▸ 0x7ffff7ffa000 ◂— jg 0x7ffff7ffa047
18
03:0018│ 0x7fffffffdcf0 —▸ 0x7ffff7fe0000 —▸ 0x7ffff7a11000 ◂— jg 0x7ffff7a11047
19
04:0020│ 0x7fffffffdcf8 —▸ 0x7ffff7ffd9f8 (_rtld_global+2456) —▸ 0x7ffff7dda000 ◂— jg 0x7ffff7dda047
20
05:0028│ 0x7fffffffdd00 ◂— 0x1
21
06:0030│ 0x7fffffffdd08 —▸ 0x7ffff7dea68d (_dl_fini+141) ◂— mov rax, qword ptr [rbp - 0x40]
22
07:0038│ 0x7fffffffdd10 ◂— 0x0
23
────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────
24
► f 0 4005cb stop
25
f 1 7ffff7dea7da _dl_fini+474
26
f 2 7ffff7a4d1a9 __run_exit_handlers+217
27
f 3 7ffff7a4d1f5
28
f 4 7ffff7a32f4c __libc_start_main+252
29
f 5 4004b9 _start+41
30
Breakpoint *0x4005cb
Copied!
看到返回地址在_dl_fini+474,所以可以得出结论,.fini_array区节的第一个函数指针在程序结束时,由_dl_fini函数调用,所以我们可加以利用。在未开启PIE的情况下,只需实现一个任意地址写,将.fini_array区节的第一个函数指针改写成后门地址或者one_gadgets,在程序结束时便能控制流程