文件启动之ELF
经常调试的朋友都知道,main函数其实不是程序的起点。程序的启动流程依次为
_start --> __libc_start_main --> main
__libc_start_main分析
对应_start
的代码,可以发现__libc_start_main
函数的参数中,有3个是函数指针:
rdi
<- main
rcx
<- __libc_csu_init
r8
<- __libc_csu_fini
不难想到,除main
以外的这两位兄弟,一位在main
开始执行前执行,一位在main
执行完毕后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pwndbg> x/20i 0x402bd0 0x402bd0 <__libc_csu_fini>: push rbp 0x402bd1 <__libc_csu_fini+1>: lea rax,[rip+0xb24e8] # 0x4b50c0 0x402bd8 <__libc_csu_fini+8>: lea rbp,[rip+0xb24d1] # 0x4b50b0 0x402bdf <__libc_csu_fini+15>: push rbx 0x402be0 <__libc_csu_fini+16>: sub rax,rbp 0x402be3 <__libc_csu_fini+19>: sub rsp,0x8 0x402be7 <__libc_csu_fini+23>: sar rax,0x3 0x402beb <__libc_csu_fini+27>: je 0x402c06 <__libc_csu_fini+54> 0x402bed <__libc_csu_fini+29>: lea rbx,[rax-0x1] 0x402bf1 <__libc_csu_fini+33>: nop DWORD PTR [rax+0x0] 0x402bf8 <__libc_csu_fini+40>: call QWORD PTR [rbp+rbx*8+0x0] 0x402bfc <__libc_csu_fini+44>: sub rbx,0x1 0x402c00 <__libc_csu_fini+48>: cmp rbx,0xffffffffffffffff 0x402c04 <__libc_csu_fini+52>: jne 0x402bf8 <__libc_csu_fini+40> 0x402c06 <__libc_csu_fini+54>: add rsp,0x8 0x402c0a <__libc_csu_fini+58>: pop rbx 0x402c0b <__libc_csu_fini+59>: pop rbp 0x402c0c <__libc_csu_fini+60>: jmp 0x48f52c <_fini>
|
利用方式 - 栈迁移
首先,看下面这条指令:
1 2
| 0x402bd8: lea rbp,[rip+0xb24d1] # 0x4b50b0 1
|
rbp = 0x4b50b0
,0x4b50b0
是fini_array
的首地址
这条指令相当于lea rbp,[fini_array]
,因此,在这里配合gadget
:
1 2 3
| leave ; (mov rsp,ebp; pop rbp) ret 12
|
便可以把__栈迁移__到fini_array
(fini_array
存储的函数指针,可能有__写权限__)
利用方式 - 控制流劫持
下面还有一条call
指令:
1 2
| 0x402bf8: call QWORD PTR [rbp+rbx*8] 1
|
rbp
即为fini_array
,因此这里将调用fini_array
中的函数
只要修改fini_array
中的值,就可以实现__控制流的转移__啦(传说中的fini_array
劫持)
这里分析的64位的静态编译程序,可见其中的__libc_csu_fini
函数简直好用的不得了鸭,既可以完成__栈迁移__,又能够劫持__控制流__
动态链接的程序__libc_csu_fini
很短,并没有上述指令…但是也有类似fini_array的函数指针
fini_array分析
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
| pwndbg> elfheader 0x400200 - 0x400224 .note.gnu.build-id 0x400224 - 0x400244 .note.ABI-tag 0x400248 - 0x400470 .rela.plt 0x401000 - 0x401017 .init 0x401018 - 0x4010d0 .plt 0x4010d0 - 0x48d630 .text 0x48d630 - 0x48f52b __libc_freeres_fn 0x48f52c - 0x48f535 .fini 0x490000 - 0x4a95dc .rodata 0x4a95dc - 0x4a95dd .stapsdt.base 0x4a95e0 - 0x4b3d00 .eh_frame 0x4b3d00 - 0x4b3da9 .gcc_except_table 0x4b5080 - 0x4b50a0 .tdata 0x4b50a0 - 0x4b50b0 .init_array 0x4b50a0 - 0x4b50e0 .tbss 0x4b50b0 - 0x4b50c0 .fini_array 0x4b50c0 - 0x4b7ef4 .data.rel.ro 0x4b7ef8 - 0x4b7fe8 .got 0x4b8000 - 0x4b80d0 .got.plt 0x4b80e0 - 0x4b9bf0 .data 0x4b9bf0 - 0x4b9c38 __libc_subfreeres 0x4b9c40 - 0x4ba2e8 __libc_IO_vtables 0x4ba2e8 - 0x4ba2f0 __libc_atexit 0x4ba300 - 0x4bba78 .bss 0x4bba78 - 0x4bbaa0 __libc_freeres_ptrs
|
其中0x4b50b0 - 0x4b50c0
即.fini_array
数组,其中存在两个函数指针:
1 2 3 4 5 6 7
| pwndbg> x/2xg 0x4b50b0 0x4b50b0: 0x0000000000401b10 0x0000000000401580 pwndbg> x/i 0x0000000000401b10 0x401b10 <__do_global_dtors_aux>: cmp BYTE PTR [rip+0xb87e9],0x0 pwndbg> x/i 0x0000000000401580 0x401580 <fini>: mov rax,QWORD PTR [rip+0xb9b71] 123456
|
1 2
| array[0]`->`__do_global_dtors_aux` `array[1]`->`fini
|
这两个函数都会在main
执行完毕后执行,因此只要__覆盖这两个函数指针,即可实现控制流的劫持__
此外,静态链接的程序也有PLT
表和GOT
表,也可以覆盖通过GOT
中的函数指针实现控制流劫持
上述fini_array
中的两个函数指针在__libc_csu_fini
(上文说的那位兄弟)中被执行
执行的顺序是array[1]->array[0]
(后有详解)
一种好玩儿的利用方式
循环大法
一种比较好玩儿的操作:
- 把
array[0]
的值覆盖为那位兄弟(__libc_csu_fini
函数)的地址
- 把
array[1]
的值覆盖为另一个函数地址,就叫他addrA
吧
于是,main
执行完毕后执行__libc_csu_fini
,于是有意思的来了!
__libc_csu_fini
先执行一遍array[1]:addrA
,返回后再执行array[0]:__libc_csu_fini
__libc_csu_fini
先执行一遍array[1]:addrA
,返回后再执行array[0]:__libc_csu_fini
__libc_csu_fini
先执行一遍array[1]:addrA
,返回后再执行array[0]:__libc_csu_fini
- …
看!连起来啦~ main
->__libc_csu_fini
->addrA
->__libc_csu_fini
->addrA
-> ......